演算法系列之四:字串的相似性

來源:互聯網
上載者:User

 

        我們把兩個字串的相似性定義為:將一個字串轉換成另外一個字串的代價(轉換的方法可能不唯一),轉換的代價越高則說明兩個字串的相似性越低。比如兩個字串:“SNOWY”和“SUNNY”,下面給出兩種將“SNOWY”轉換成“SUNNY”的方法:

 

變換1:

    S - N O W Y

    S U N N - Y

   

   Cost = 3 (插入U、替換O、刪除W)

 

變換2:

    - S N O W - Y

    S U N - - N Y

   

    Cost = 5 (插入S、替換S、刪除O、刪除W、插入N)

分析問題

        我們可以把這種相似性理解為:把一個字串(source)通過“插入、刪除和替換”這樣的編輯操作變成另外一個字串(target)所需要的最少編輯次數,也就是兩個字串之間的編輯距離(edit distance)。能否給出一個演算法,求解任意兩個字串之間的編輯距離?

        從題目給出的例子可知,將一個字串經由插入、刪除或替換等操作轉換成另外一個字串的方法不止一種,所需要做的編輯次數也不相同,如果有某種方法能夠用最小的修改次數完成轉換,這種方法的編輯次數就是我們要求的編輯距離。很顯然,這是個求最優解的問題。

        提到最優解問題,首先可以考慮使用貪婪法,但是本題顯然是一個多階段決策類型的最優解問題,對source字串做最小的修改變換到target字串,需要在處理過程中的每個階段都選擇修改最小的方式,但是本題中每個階段之間都不是孤立的,受到前面已經確定的決策和後面可選的決策共同影響,無法通過對每一次決策的最優決策簡單堆疊出最後的最優結果,因此,排除貪婪法。

動態規劃法(Dynamic Programming)

        對於多階段決策類型的問題,應該優先考慮動態規劃法(Dynamic Programming, DP)。動態規劃法是解決多階段決策最佳化問題常用的一種思想方法[1],也是所有解題方法中最抽象的一種方法。使用動態規劃法解決問題的關鍵有兩點,一點是定義子問題的最優子結構【註解1】,另一點是確定子問題最優解的堆疊。定義最優子結構就是分解子問題,可以用遞推的方式,也可以用遞迴的方式,基本原則就是將問題分成M個子問題,同時確定每個子問題的最優解與其它N(N小於M)個子問題之間的關係。子問題最優解的堆疊是指最優決策序列和它的子序列的遞推關係,包括子問題最優解的遞推關係和邊界值兩部分。對於一個問題,如果能夠找最優子結構的定義方式(包括子問題之間的關係)和子問題最優解的堆疊,並且每個子問題最優解都滿足無後效性【註解2】,則該問題就可以嘗試用動態規劃法解決這個問題。

        以本題為例,假設source字串有n個字元,target字串有m個字元,如果將問題定義為求解將source的1-n個字元轉換為target的1-m個字元所需要的最少編輯次數(最小編輯距離),則其子問題就可以定義為將source的1-i個字元轉換為target的1-j個字元所需要的最少編輯次數,這就是本問題的最優子結構。我們用d[i, j]表示source[1..i]到target[1..j]之間的最小編輯距離,則計算d[i,
j]的遞推關係可以這樣計算出來:

 

如果source[i] 等於target[j],則:

 

d[i, j] = d[i, j] + 0                                               (遞推式 1)

 

如果source[i] 不等於target[j],則根據插入、刪除和替換三個策略,分別計算出使用三種策略得到的編輯距離,然後取最小的一個:

 

d[i, j] = min(d[i, j - 1] + 1,d[i - 1, j] + 1,d[i - 1, j - 1] + 1 )            (遞推式 2)

 

d[i, j - 1] + 1 表示對source[i]執行插入操作後計算最小編輯距離

d[i - 1, j] + 1 表示對source[i]執行刪除操作後計算最小編輯距離

d[i - 1, j - 1] + 1表示對source[i]替換成target[i]操作後計算最小編輯距離

 

d[i, j]的邊界值就是當target為空白字串(m = 0)或source為空白字串(n = 0)時所計算出的編輯距離:

 

m = 0,對於所有 i:d[i, 0] = i

n = 0,對於所有 j:d[0, j] = j

 

        根據前面分析的最優子結構、最優解的遞推關係以及邊界值,寫出用動態規劃法求解最小編輯距離的演算法就很容易了,以下代碼就是計算兩個字串的最小編輯距離的演算法實現:

 

30 /*注意:source和target字串的長度不能超過d矩陣的限制*/

31 int EditDistance(const std::string& source,
const std::string& target)

32 {

33     std::string::size_type i,j;

34    
int d[MAX_STRING_LEN][MAX_STRING_LEN]
= {
0 };

35 

36    
for(i =
0; i <= source.length(); i++)

37         d[i][0]
= i;

38    
for(j =
0; j <= target.length(); j++)

39         d[0][j]
= j;

40 

41    
for(i =
1; i <= source.length(); i++)

42    
{

43        
for(j =
1; j <= target.length(); j++)

44        
{

45            
if((source[i
- 1]
== target[j
- 1]))

46            
{

47                 d[i][j]
= d[i
- 1][j
- 1];
//不需要編輯操作

48            
}

49            
else

50            
{

51                
int edIns = d[i][j
- 1]
+ 1;
//source 插入字元

52                
int edDel = d[i
- 1][j]
+ 1;
//source 刪除字元

53                
int edRep = d[i
- 1][j
- 1]
+ 1;
//source 替換字元

54 

55                 d[i][j]
= std::min(std::min(edIns, edDel), edRep);

56            
}

57        
}

58    
}

59 

60    
return d[source.length()][target.length()];

61 }

窮舉法(枚舉法)

         除了貪婪法和動態規劃法,窮舉法也是求解最優解的常用方法。窮舉法比動態規劃法容易理解,窮舉法的原理就是對問題域的整個解空間進行搜尋,通過比較所有可能的解,從中選出最優解。窮舉法的本意其實是為了求解某個問題的所有合法的解,最優解可以理解為只是搜尋過程中的一個副產品。根據題目的不同,窮舉法的實現也不一樣,如果問題的解空間是線性結構,則可以使用迴圈方法,如果問題的解空間是樹狀結構,則可以使用遞迴方法。本問題的解空間顯然不是線性結構,因此考慮使用遞迴方法對所有解進行窮舉。遞迴演算法需要解決兩個問題,一個是如何將問題遞迴地分解為子問題,另一個問題是如何確定遞迴終止條件。

        對於本文的問題,可以這樣對遞迴分解子問題:位置i表示source和target字串中共同的字元位置,對於每一個i位置,計算從i位置開始的子串的編輯距離,計算方法是比較source[i]和target[i]的值,如果相等,則表示這個位置需要的編輯次數是0,i位置開始的子串的編輯距離就等於source和target字串從i + 1位置開始的子串的編輯距離。如果不相等,則對source字串i位置的字元分別嘗試插入、刪除和替換三種編輯方式計算新的子串的編輯距離,然後加上1就是從i位置開始的子串的編輯距離。用插入、刪除和替換三種方式計算新的子串的開始位置需要分別調整,如果是在i位置插入字元,則source字串的i位置不變化,target字串的i位置移到i
+ 1位置繼續。如果是在i位置刪除字元,則source字串的i位置移到i + 1位置,target字串的i位置不變。如果是在i位置替換字元,則source和target字串的i位置都移到i + 1位置。

        有了子問題的遞迴分解,還需要確定遞迴計算的終止條件。本問題的終止條件很簡單,由上面的遞迴子問題定義可知,當source和target字串中的某一個為空白時即可終止遞迴。當達到遞迴終止條件時,編輯距離的值就是source和target字串中非空的那一個剩餘子串的長度,這一點比較容易理解,如果source最後剩餘的子串不為空白,則意味著需要刪除這些字元才能和target一樣,如果target最後剩餘的子串不為空白,則意味著需要對source插入這些字元才能和target一樣,所以最後的編輯距離就是剩餘子串的長度。

        根據以上對遞迴子問題的分解方法和遞迴終止條件的分析,可以很容易地寫出使用遞迴方法求解本問題的演算法實現。遞迴演算法通常效率不高,但是與人類解決問題的思維方式一致,通常代碼簡潔,容易理解,本文給出的演算法實現只用了9行代碼:

14 int EditDistance(const std::string& source,
const std::string& target)

15 {

16    
if(source.empty()
|| target.empty())

17        
return std::abs(source.length()
- target.length());

18 

19    
if(source[0]
== target[0])

20        
return EditDistance(source.substr(1), target.substr(1));

21 

22    
int edIns = EditDistance(source, target.substr(1))
+ 1;
//source 插入字元

23    
int edDel = EditDistance(source.substr(1), target)
+ 1;
//source 刪除字元

24    
int edRep = EditDistance(source.substr(1), target.substr(1))
+ 1;
//source 替換字元

25 

26    
return std::min(std::min(edIns, edDel), edRep);

27 }

總結

        就時間複雜度而言,動態規劃法的時間複雜度是O(n2),窮舉演算法的時間複雜度在最好的情況下是O(n),也就是每次都走(source[0] == target[0])分支的情況,最差的情況當然是O(3n)。這種時間複雜度是指數型的演算法,基本上是停用演算法,只存在理論上的價值,實際工程中是不會適用這種時間複雜度的演算法。

        就空間複雜度而言,動態規劃法的空間複雜度是O(mn),但是這個空間複雜度在很大程度上是可以最佳化的,最佳化的程度因演算法而異。以本文的演算法為例,從(遞推式 2)可以看到,d[i, j]的結果只需要知道i,i - 1,以及j,j – 1位置上的結果就可以遞推計算出來,其它位置上的資訊完全可以在計算完成後釋放,並不需要從頭到尾佔用m x n的空間。類似的最佳化只是資料群組織上的一些小技巧,本文就不再贅述,有興趣的讀者可以自己對EditDistance()函數進行改造。

 

  

註解:

【1】最優子結構:對於多階段決策問題,如果每一個階段的最優決策序列的子序列也是最優的,且決策序列具有“無後效性”,就可以將此決策方法理解為最優子結構。

 

【2】無後效性:動態規劃法的最優解通常是由一系列最優決策組成的決策序列,最優子結構就是這些最優決策序列中的一個子序列,對於每個子序列再做最優決策會產生新的最優決策(子)序列,如果某個決策只受當前最優決策子序列的影響,而不受當前決策可能產生的新的最優決策子序列的影響,則可以理解這個最優決策具有無後效性。

 

  

參考文獻:

[1].演算法藝術和資訊學競賽.劉汝佳、黃亮.清華大學出版社.2003

[2]. http://en.wikipedia.org/wiki/Edit_distance

[3]. http://en.wikipedia.org/wiki/Levenshtein_distance

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.