動態規劃法(十一)編輯距離
編輯距離問題
??什麽是兩個字符串的編輯距離(edit distance)?給定字符串s1和s2,以及在s1上的如下操作:
- 插入(Insert)一個字符
- 移除(Remove)一個字符
- 替換(Replace)一個字符
試問最小需要多少次這樣的操作才能使得s1轉換為s2?
??比如,單詞“cat”和“hat”,這樣的操作最少需要一次,只需要把“cat”中的“c”替換為“h”即可。單詞“recall”和“call”,這樣的操作最少需要兩次,只需要把“recall”中的“r”和“e”去掉即可。單詞“Sunday”和“Saturday”,這樣的操作最少需要3次,在“Sunday”的“S”和“u”中插入“a”和“t”,再把“n”替換成“r”即可。
??那麽,是否存在一種高效的算法,能夠快速、準確地計算出兩個字符串的編輯距離呢?
動態規劃算法
??我們使用動態規劃算法(Dynamic Programming)來計算出兩個字符串的編輯距離。
??我們從兩個字符串s1和s2的最末端向前遍歷來考慮。假設s1的長度為m,s2的長度為n,算法如下:
- 如果兩個字符串的最後一個字符一樣,那麽,我們就可以遞歸地計算長度為m-1和n-1的兩個字符串的情形;
- 如果兩個字符串的最後一個字符不一樣,那麽,進入以下三種情形:
- 插入: 遞歸地計算長度為m和n-1的兩個字符串的情形,這是因為在s1中的末端插入了一個s2的最後一個字符,這樣s1和s2的末端字符一樣,就是1中情形;
- 刪除: 遞歸地計算長度為m-1和n的兩個字符串的情形,這是在s1中的末端刪除了一個字符;
- 替換: 遞歸地計算長度為m-1和n-1的兩個字符串的情形,這是因為把s1中末端字符替換成了s2的最後一個字符,這樣s1和s2的末端字符一樣,就是1中情形;
??這樣,我們就有了子結構問題。對於動態規劃算法,我們還需要一個初始化的過程,然後中間維護一張二維表即可。初始化的過程如下: 如果m為0,則至少需要操作n次,即在s1中逐個添加s2的字符,一共是n次;如果n為0,則至少需要操作m次,即把s1的字符逐個刪除即可,一共是m次。
Python實現
??利用DP算法解決兩個字符串的編輯距離的Python代碼如下:
# -*- coding: utf-8 -*- # using Dynamic Programming to solve edit distance problem # s1, s2 are two strings def editDistDP(s1, s2): m, n = len(s1), len(s2) # Create a table to store results of subproblems dp = [[0 for _ in range(n+1)] for _ in range(m+1)] # using DP in bottom-up manner for i in range(m + 1): for j in range(n + 1): # If first string is empty, only option is to # isnert all characters of second string, thus the # min opration is j if i == 0: dp[i][j] = j # If second string is empty, only option is to # remove all characters of second string, thus the # min opration is i elif j == 0: dp[i][j] = i # If last characters are same, ignore last character # and recursive for remaining string elif s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] # If last character are different, consider all # possibilities and find minimum of inserting, removing, replacing else: dp[i][j] = 1 + min(dp[i][j-1], # Insert dp[i-1][j], # Remove dp[i-1][j-1]) # Replace return dp[m][n] # Driver program s1 = "sunday" s2 = "saturday" edit_distance = editDistDP(s1, s2) print("The Edit Distance of ‘%s‘ and ‘%s‘ is %d."%(s1, s2, edit_distance))
輸出結果如下:
The Edit Distance of ‘sunday‘ and ‘saturday‘ is 3.
Java實現
??利用DP算法解決兩個字符串的編輯距離的Java代碼如下:
package DP_example;
// 計算兩個字符串的編輯距離(Edit Distance)
public class Edit_Distance {
// 主函數
public static void main(String[] args) {
String str1 = "cat";//"Sunday";
String str2 = "hat";//"Saturday";
int edit_dist = edit_distance(str1, str2);
System.out.println(String.format("The edit distance of ‘%s‘ and ‘%s‘ is %d.",
str1, str2, edit_dist));
}
/*
函數edit_distanc: 計算兩個字符串的編輯距離(Edit Distance)
傳入參數: 兩個字符串str1和str2
返回: 編輯距離
*/
public static int edit_distance(String str1, String str2){
// 字符串的長度
int m = str1.length();
int n = str2.length();
// 初始化表格,用於維護子問題的解
int[][] dp = new int[m+1][n+1];
for(int i=0; i <= m; i++)
for(int j=0; j <= n; j++)
dp[i][j] = 0;
// using DP in bottom-up manner
for(int i=0; i <= m; i++){
for(int j=0; j <= n; j++) {
/* If first string is empty, only option is to
* isnert all characters of second string, thus the
* min opration is j
*/
if(i == 0) { dp[i][j] = j;}
/* If second string is empty, only option is to
* remove all characters of second string, thus the
* min opration is i
*/
else if(j == 0){dp[i][j] = i;}
/* If last characters are same, ignore last character
* and recursive for remaining string
*/
else if(str1.charAt(i-1) == str2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}
/*If last character are different, consider all
*possibilities and find minimum of inserting, removing, replacing
*/
else{
/*
* dp[i][j-1]: Insert
* dp[i-1][j]: Remove
* dp[i-1][j-1]: Replace
*/
dp[i][j] = 1 + min(min(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]);
}
}
}
return dp[m][n];
}
public static int min(int i, int j){
return (i <= j) ? i : j;
}
}
輸出結果如下:
The edit distance of ‘cat‘ and ‘hat‘ is 1.
其它實現方式
??以上,我們用Python和Java以及動態規劃算法自己實現了編輯距離的計算。當然,我們也可以調用第三方模塊的方法,比如NTLK中的edit_distance()函數,示例代碼如下:
# 利用NLTK中的edit_distance計算兩個字符串的Edit Distance
from nltk.metrics import edit_distance
s1 = "recall"
s2 = "call"
t = edit_distance(s1, s2)
print("The Edit Distance of ‘%s‘ and ‘%s‘ is %d." % (s1, s2, t))
輸出結果如下:
The Edit Distance of ‘recall‘ and ‘call‘ is 2.
總結
??在本文中,我們對於兩個字符串的編輯距離的計算,只采用了插入、刪除、替換這三種操作,在實際中,可能還會有更多的操作,比如旋轉等。當然,這並不是重點,重點是我們需要了解解決這類問題的算法,即動態規劃算法。在後續的文章中,筆者會介紹編輯距離在文本處理中的應用。
??本次分享到此結束,歡迎大家交流~~
註意:本人現已開通微信公眾號: Python爬蟲與算法(微信號為:easy_web_scrape), 歡迎大家關註哦~~
動態規劃法(十一)編輯距離