字串的編輯距離也被稱為距Levenshtein距離(Levenshtein Distance),屬於經典演算法,常用方法使用遞迴,更好的方法是使用動態規劃演算法,以避免出現重疊子問題的反覆計算,減少系統開銷。
《程式設計之美》一書中3.3節中計算兩個字串的相似度,歸根到底也是要求兩個字串的距離,其中問題是這樣提出的:
許多程式會大量使用字串。對於不同的字串,我們希望能夠有辦法判斷其相似程式。我們定義一套操作方法來把兩個不相同的字串變得相同,具體的操作方法為:
- 修改一個字元(如把"a"替換為"b");
- 增加一個字元(如把"abdd"變為"aebdd");
- 刪除一個字元(如把"travelling"變為"traveling");
比如,對於"abcdefg"和"abcdef"兩個字串來說,我們認為可以通過增加/減少一個"g"的方式來達到目的。上面的兩種方案,都僅需要一 次 。把這個操作所需要的次數定義為兩個字串的距離,而相似度等於"距離+1"的倒數。也就是說,"abcdefg"和"abcdef"的距離為1,相似度 為1/2=0.5。給定任意兩個字串,你是否能寫出一個演算法來計算它們的相似度呢?
其實這個問題的關鍵是要求兩個字串的編輯距離。
例如 將kitten一字轉成sitting:
-
sitten (k→s)
-
sittin (e→i)
-
sitting (→g)
俄羅斯科學家Vladimir Levenshtein在1965年提出這個概念。
問題:找出字串的編輯距離,即把一個字串s1最少經過多少步操作變成程式設計字串s2,操作有三種,新增一個字元,刪除一個字元,修改一個字元。
下面我們就針對這個問題來詳細闡述一下:
我們假定函式dist(str1, str2)表示字串str1轉變到字串str2的編輯距離,那麼對於下面3種極端情況,我們很容易給出解答(0表示空串)。
-
dist(0, 0) = 0
-
dist(0, s) = strlen(s)
-
dist(s, 0) = strlen(s)
對於一般的情況,dist(str1, str2)我們應該如何求解呢?
假定我們現在正在求解dist(str1+char1, str2+char2),也就是把"str1+char1"轉變成"str2+char2"。在這個轉變過稱中,我們要分情況討論:
-
str1可以直接轉變成str2。這時我們只要把char1轉成char2就可以了(如果char1 != char2)。
-
str1+char1可以直接轉變成str2。這時我們處理的方式是插入char2。
-
str1可以直接轉成str2+char2。這時的情況是我們需要刪除char1。
綜合上面三種情況,dist(str1+char1, str2+char2)應該是三者的最小值。
解析:
首先定義這樣一個函式——edit(i, j),它表示第一個字串的長度為i的子串到第二個字串的長度為j的子串的編輯距離。
顯然可以有如下動態規劃公式:
-
if i == 0 且 j == 0,edit(i, j) = 0
-
if i == 0 且 j > 0,edit(i, j) = j
-
if i > 0 且j == 0,edit(i, j) = i
-
if i ≥ 1 且 j ≥ 1 ,edit(i, j) == min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) },當第一個字串的第i個字元不等於第二個字串的第j個字元時,f(i, j) = 1;否則,f(i, j) = 0。
我們建立以下表格,將兩個字串按照表格1所示的樣子進行擺放,規則按照以上公式進行輸入,如下所示,我們可以得到每個表格中的值,如下表格2所示:
0 |
a |
b |
c |
d |
e |
f |
|
0 |
|
|
|
|
|
|
|
a |
|
|
|
|
|
|
|
c |
|
|
|
|
|
|
|
e |
|
|
|
|
|
|
|
表格1(字串擺放表格)
0 |
a |
b |
c |
d |
e |
f |
|
0 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
a |
1 |
|
|
|
|
|
|
c |
2 |
|
|
|
|
|
|
e |
3 |
|
|
|
|
|
|
表格2(按照規則計算i==0 或 j==0的情況)
計算edit(1, 1),edit(0, 1) + 1 == 2,edit(1, 0) + 1 == 2,edit(0, 0) + f(1, 1) == 0 + 1 == 1,min(edit(0, 1),edit(1, 0),edit(0, 0) + f(1, 1))==1,因此edit(1, 1) == 1。依次類推,有如下表格3所示最終的矩陣:
0 |
a |
b |
c |
d |
e |
f |
|
0 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
a |
1 |
0 |
1 |
2 |
3 |
4 |
5 |
c |
2 |
1 |
1 |
1 |
2 |
3 |
4 |
e |
3 |
2 |
2 |
2 |
2 |
2 |
3 |
表格3(最終計算得到的字串相對距離)
此時右下角即為我們所需要的兩個字串的編輯距離。即字串 "abcdef"和"ace"的編輯距離為3.
有了以上的步驟,相信大家已經很清楚了,使用動態規劃演算法的時候,需要建立子問題的表格,以上的表格就是。而且我們能夠很容易的使用二維陣列建立。程式碼實現也就易如反掌了!
以下是我的實現過程,希望對大家有用,如果有什麼可以優化或者錯誤的地方,希望能夠得到批評指正。
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 int min3Value(int a, int b, int c) 7 { 8 int tmp = (a <= b? a:b); 9 return (tmp<=c? tmp: c); 10 } 11 12 13 int Get2StringEditDis(string strA, string strB) 14 { 15 int nLenA = strA.length(); 16 int nLenB = strB.length(); 17 int **matrix = new int *[nLenA + 1]; 18 for (int i = 0; i != nLenA +1; i++) 19 { 20 matrix[i] = new int[nLenB + 1]; 21 } 22 // 動態規劃 計算 23 // 初始化陣列 24 matrix[0][0] = 0; 25 int p,q; 26 // j = 0; edit(i, j) = i 27 for (p = 1; p!= nLenA+1; p++) 28 { 29 matrix[p][0] = p; 30 } 31 // i = 0; edit(i,j) = j 32 for (q=1; q != nLenB+1; q++) 33 { 34 matrix[0][q] = q; 35 } 36 // i>0, j>0 37 for (int j = 1; j != nLenA+1; j++) 38 { 39 for (int k = 1; k != nLenB+1; k++) 40 { 41 int Fjk = 0; 42 if (strA[j-1] != strB[k-1]) 43 { 44 Fjk = 1; 45 } 46 matrix[j][k] = min3Value(matrix[j-1][k]+1,matrix[j][k-1]+1,matrix[j-1][k-1]+Fjk); 47 } 48 } 49 50 51 52 53 // 輸出距離矩陣 54 // 第一行輸出字串b 55 // 第一列輸出字串A 56 cout<<"*****************************"<<endl; 57 cout<<"字串編輯距離矩陣如下:\n"; 58 for (p = -1; p!= nLenA +1; p++) 59 { 60 for (q = -1; q !=nLenB+1; q++) 61 { 62 //cout.width(3),cout<<matrix[p][q]; 63 cout.width(3); 64 if (p ==-1 && q == -1) 65 { 66 cout<<" "; 67 } 68 else if (p + q == -1) 69 { 70 cout<<"NUL"; 71 } 72 else if (p == -1 && q >0) 73 { 74 cout<<strB[q-1]; 75 } 76 else if(q == -1 && p > 0) 77 { 78 cout<<strA[p-1]; 79 } 80 else 81 { 82 cout<<matrix[p][q]; 83 } 84 } 85 cout<<endl; 86 } 87 cout<<"*****************************"<<endl; 88 // 89 int nEditDis = matrix[nLenA][nLenB]; 90 for (int m = 0; m!=nLenA + 1; m++) 91 { 92 delete[] matrix[m]; 93 } 94 delete[] matrix; 95 96 97 return nEditDis; 98 } 99 100 101 int main() 102 { 103 string strA("abcdefgh"); 104 string strB("adgcf"); 105 106 int nDist = Get2StringEditDis(strA,strB); 107 cout<<"The edit dis is "<<nDist<<endl; 108 109 return 0; 110 }
結果如圖1所示:
其中對於另一篇隨筆中有亞馬遜今年的線上筆試題中,有一道該型別題目的變種~~~ 大家可以翻閱下!地址: http://www.cnblogs.com/jiabei521/p/3352935.html