编辑距离又称Leveinshtein距离,是由俄罗斯科学家Vladimir Levenshtein在1965年提出。
以字符串为例,字符串a和字符串b的编辑距离是将a转换成b的最小操作次数,这里的操作包括三种:
插入一个字符
删除一个字符
替换一个字符
举个例子,
计算learning和meaning的编辑距离,需要下列步骤
至少要做2次操作,编辑距离为2
用 e d a , b ( i , j ) ed_{a,b}(i,j) eda,b(i,j)来表示a,b字符串的编辑距离,其中i代表a的长度,j代表b的长度
e d a , b ( i , j ) = { m i n ( i , j ) = 0 max ( i , j ) a i = b j e d a , b ( i − 1 , j − 1 ) a i < > b j min ( e d a , b ( i − 1 , j − 1 ) + 1 , e d a , b ( i − 1 , j ) + 1 , e d a , b ( i , j − 1 ) + 1 ) ed_{a,b}(i,j)=\left\{ \begin{aligned} min(i,j) =0 && \max(i,j) \\ a_i = b_j && ed_{a,b}(i-1,j-1)\\ a_i <> b_j && \min(ed_{a,b}(i-1,j-1)+1,ed_{a,b}(i-1,j)+1,ed_{a,b}(i,j-1)+1) \end{aligned} \right. eda,b(i,j)=⎩⎪⎨⎪⎧min(i,j)=0ai=bjai<>bjmax(i,j)eda,b(i−1,j−1)min(eda,b(i−1,j−1)+1,eda,b(i−1,j)+1,eda,b(i,j−1)+1)
e d a , b ( i , j ) ed_{a,b}(i,j) eda,b(i,j)的值描述如下:
在前面的公式中,我们可以很明显的感觉到可以用一个二维矩阵来表达该距离
我们用learn(a)和mean(b)比较简单的单词用矩阵来表达:
0 | m | e | a | n | |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 |
l | 1 | ||||
e | 2 | ||||
a | 3 | ||||
r | 4 | ||||
n | 5 |
0 | m | e | a | n | |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 |
l | 1 | m!=l 所以取min(0+1,1+1,1+1)=1 | |||
e | 2 | ||||
a | 3 | ||||
r | 4 | ||||
n | 5 |
0 | m | e | a | n | |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 |
l | 1 | 1 | e!=l 所以取min(1+1,2+1,1+1)=2 | ||
e | 2 | ||||
a | 3 | ||||
r | 4 | ||||
n | 5 |
如此持续迭代,直至推导最终结果
0 | m | e | a | n | |
---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 |
l | 1 | 1 | 2 | 3 | 4 |
e | 2 | 2 | 1 | 2 | 3 |
a | 3 | 3 | 2 | 1 | 2 |
r | 4 | 4 | 3 | 2 | 2 |
n | 5 | 5 | 4 | 3 | 2 |
最后的编辑距离就是2
当i
为何复杂度不是O ( i ∗ j ) (i*j) (i∗j)? 为何最小空间复杂度是O ( 2 ∗ i ) (2*i) (2∗i) 优化后的代码如下:
当我们只是计算编辑距离的时候,只是求矩阵的最后一个值。当字符串的长度i
在我们计算的时候,发现只是需要2行也就是当前行和前一行的状态,所以没必要保存完整的矩阵,而只需要保存2行矩阵public static int editDistance(String left, String right) {
if(left.length() > right.length()) {
String tmp = left;
left = right;
right = tmp;
}
int x,y,gap = 0;
if(right.length() == left.length()) {
x = left.length();
y = x;
} else {
x = left.length()+1;
y = left.length();
gap = right.length() - left.length() -1;
}
int[] pre = new int[ y + 1];
int[] cur = new int[ y + 1];
for (int i = 0; i < pre.length; i++) {
pre[i] = i;
}
for (int i = 1; i <= x; i++) {
cur[0] = i;
for (int j = 1; j <= y; j++) {
if (right.charAt(i - 1) == left.charAt(j - 1)) {
cur[j] = pre[j - 1];
} else {
cur[j] = Math.min(cur[j - 1], Math.min(pre[j], pre[j - 1])) + 1;
}
}
int[] tmp = pre;
pre = cur;
cur = tmp;
}
return pre[pre.length - 1]+gap;
}