字串編輯距離問題詳解

菜鳥加貝的爬升發表於2013-10-06

  字串的編輯距離也被稱為距Levenshtein距離(Levenshtein Distance),屬於經典演算法,常用方法使用遞迴,更好的方法是使用動態規劃演算法,以避免出現重疊子問題的反覆計算,減少系統開銷。

《程式設計之美》一書中3.3節中計算兩個字串的相似度,歸根到底也是要求兩個字串的距離,其中問題是這樣提出的:

  許多程式會大量使用字串。對於不同的字串,我們希望能夠有辦法判斷其相似程式。我們定義一套操作方法來把兩個不相同的字串變得相同,具體的操作方法為:

  •         修改一個字元(如把"a"替換為"b");
  •         增加一個字元(如把"abdd"變為"aebdd");
  •         刪除一個字元(如把"travelling"變為"traveling");

    比如,對於"abcdefg"和"abcdef"兩個字串來說,我們認為可以通過增加/減少一個"g"的方式來達到目的。上面的兩種方案,都僅需要一 次 。把這個操作所需要的次數定義為兩個字串的距離,而相似度等於"距離+1"的倒數。也就是說,"abcdefg"和"abcdef"的距離為1,相似度 為1/2=0.5。給定任意兩個字串,你是否能寫出一個演算法來計算它們的相似度呢?    

  其實這個問題的關鍵是要求兩個字串的編輯距離

例如 將kitten一字轉成sitting:

  1. sitten (k→s)
  2. sittin (e→i)
  3. sitting (→g)

俄羅斯科學家Vladimir Levenshtein1965年提出這個概念。

問題:找出字串的編輯距離,即把一個字串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"。在這個轉變過稱中,我們要分情況討論:

  1. str1可以直接轉變成str2。這時我們只要把char1轉成char2就可以了(如果char1 != char2)。
  2. str1+char1可以直接轉變成str2。這時我們處理的方式是插入char2
  3. str1可以直接轉成str2+char2。這時的情況是我們需要刪除char1

  綜合上面三種情況,dist(str1+char1, str2+char2)應該是三者的最小值。

解析:

首先定義這樣一個函式——edit(i, j),它表示第一個字串的長度為i的子串到第二個字串的長度為j的子串的編輯距離。

顯然可以有如下動態規劃公式:

  • if i == 0 j == 0edit(i, j) = 0
  • if i == 0 j > 0edit(i, j) = j
  • if i > 0 j == 0edit(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

2

3

 4

 5

 6

a

 1

  

  

  

  

  

  

c

 2

  

  

  

  

  

  

e

 3

  

  

  

  

  

  

      表格2(按照規則計算i==0 或 j==0的情況)

 計算edit(1, 1)edit(0, 1) + 1 == 2edit(1, 0) + 1 == 2edit(0, 0) + f(1, 1) == 0 + 1 == 1min(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

相關文章