编辑距离(Edit Distance),这里指的是Levenshtein距离,也就是字符串S1通过插入、修改、删除三种操作最少能变换成字符串S2的次数。接下来介绍利用动态规划来求解字符串的编辑距离。
定义:和表示两字符串,表示字符串的前个字符串和的前个字符串的编辑距离,和分别表示的第个字符和的第字符。
- 若,则就等于的前个字符串和的前个字符串的编辑距离即
- 若,则为了使,可以通过在的第个字符处插入的第个字符,或替换的第个字符为的第个字符,或删除的第个字符(即使删除之后还是会出现不同),但是上述都是在进行了一次字符的操作之后,将转化为字问题求解,如上述的替换使,则 ,插入和删除使 或者 。
基于上述的情况可以得出递推公式:
例如:,,则利用动态规划求解的矩阵为
| a | b | f | c | e | ||
|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 | 5 | |
| a | 1 | 0 | 1 | 2 | 3 | 4 |
| b | 2 | 1 | 0 | 1 | 2 | 3 |
| c | 3 | 2 | 1 | 1 | 1 | 2 |
| d | 4 | 3 | 2 | 2 | 2 | 2 |
代码实现
public static int levenshtein(String s1, String s2){
if(s1 == null){
return s2 == null? 0:s2.length();
}
if(s2 == null){
return s1 == null? 0:s1.length();
}
int n = s1.length();
int m = s2.length();
int[][] matrix = new int[n+1][m+1];
for(int i = 0;i <= n;i++){
matrix[i][0] = i;
}
for(int j = 0;j <= m;j++){
matrix[0][j] = j;
}
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
if(s1.charAt(i-1) == s2.charAt(j-1)){
matrix[i][j] = matrix[i-1][j-1];
}else {
matrix[i][j] = Math.min(Math.min(matrix[i-1][j], matrix[i][j-1]), matrix[i-1][j-1]) + 1;
}
}
}
return matrix[n][m];
}
上述的方法其中有一个不足之处是当两个字符串太长时,则对应申请的数组占用的空间也变大。而上述的算法中,在更新的过程中只用到了、、这三个数,因此我们可以只存储更新的上一行的数据,这样就可以得到,这两个数,而,可以在此基础上根据上一行的和推出,在结合上一行的和推出…以此类推得到第行的数据。接下来是改进后的代码实现
public static int levenshteinImprove(String s1, String s2){
if(s1 == null)
return s2 == null? 0:s2.length();
if(s2 == null)
return s1 == null? 0:s1.length();
int n = s1.length(),m = s2.length();
int[] matrix = new int[m+1];
for(int i = 0;i <= m;i++){
matrix[i] = i;
}
for(int i = 1;i <= n;i++){
int pre = matrix[0];
matrix[0] = i;
for(int j = 1;j <= m;j++){
int tmp = matrix[j];
if(s1.charAt(i-1) == s2.charAt(j-1)){
matrix[j] = pre;
}else {
matrix[j] = Math.min(Math.min(pre, matrix[j-1]), matrix[j]) + 1;
}
pre = tmp;
}
}
return matrix[m];
}
上述代码还可以改进一个小细节,当比较的字符串长短一个很长,一个很短时,我们可以比较其长短,取较短的字符串长度作为开辟的数组的长度。