编辑距离又称Levenshtein距离,是指将一个字符串转为另一个字符串所需的字符编辑次数,包括以下三种操作:
最小编辑距离是指将一个字符串转为另一个字符串所需的最少字符编辑次数。
计算最小编辑距离需要用到动态规划。对于两个字符串 A A A和 B B B,设 f ( i , j ) f(i, j) f(i,j)表示将 A A A的前 i i i个字符构成的子串转为 B B B的前 j j j个字符构成的子串所需的最少次数,那么 f ( i , 0 ) = i f(i, 0) = i f(i,0)=i, f ( 0 , j ) = j f(0, j) = j f(0,j)=j,从 A A A到 B B B的最小编辑距离即 f ( m , n ) f(m, n) f(m,n), m m m和 n n n分别为 A A A和 B B B的长度。
由于 f ( i , 0 ) f(i, 0) f(i,0)和 f ( 0 , j ) f(0, j) f(0,j)已确定,可以考虑从 f ( i , j ) f(i, j) f(i,j)递推到 f ( i + 1 , j ) f(i+1, j) f(i+1,j)、 f ( i , j + 1 ) f(i, j+1) f(i,j+1)和 f ( i + 1 , j + 1 ) f(i+1, j+1) f(i+1,j+1),或者反过来,考虑从 f ( i − 1 , j ) f(i-1, j) f(i−1,j)、 f ( i , j − 1 ) f(i, j-1) f(i,j−1)、 f ( i − 1 , j − 1 ) f(i-1, j-1) f(i−1,j−1)递推到 f ( i , j ) f(i, j) f(i,j)。显然,前两种情况只需要在最后插入一个字符,即 f ( i , j ) = f ( i − 1 , j ) + 1 f(i, j) = f(i-1, j)+1 f(i,j)=f(i−1,j)+1, f ( i , j ) = f ( i , j − 1 ) + 1 f(i, j) = f(i, j-1)+1 f(i,j)=f(i,j−1)+1。而最后一种情况需要比较 A A A的第 i i i个字符和 B B B的第 j j j个字符,若相等,则 f ( i , j ) = f ( i − 1 , j − 1 ) f(i, j) = f(i-1, j-1) f(i,j)=f(i−1,j−1);否则需要进行替换操作,即 f ( i , j ) = f ( i − 1 , j − 1 ) + 1 f(i, j) = f(i-1, j-1)+1 f(i,j)=f(i−1,j−1)+1。因此 f ( i , j ) f(i, j) f(i,j)取三者最小值,递推公式为
f ( i , j ) = m i n { ( f ( i − 1 , j ) + 1 f ( i , j − 1 ) + 1 f ( i − 1 , j − 1 ) + { 0 , A [ i ] = B [ j ] 1 , o t h e r w i s e f(i,j) = min \begin{cases} (f(i-1, j)+1 \\ f(i, j-1)+1 \\ f(i-1, j-1)+ \begin{cases} 0, A[i] = B[j] \\ 1, otherwise \end{cases} \end{cases} f(i,j)=min⎩⎪⎪⎪⎨⎪⎪⎪⎧(f(i−1,j)+1f(i,j−1)+1f(i−1,j−1)+{0,A[i]=B[j]1,otherwise
时间复杂度和空间复杂度均为 O ( m n ) O(mn) O(mn),其中 m m m和 n n n分别为 A A A和 B B B的长度。
完整代码见Github。
def Levenshtein(str1, str2):
m = len(str1) + 1
n = len(str2) + 1
dp = [[0 for _ in range(n)] for _ in range(m)]
for i in range(m):
dp[i][0] = i
for j in range(n):
dp[0][j] = j
for i in range(1, m):
for j in range(1, n):
if (str1[i-1] == str2[j-1]):
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
return dp[m-1][n-1]