最近因为在做短文本字符串相似度比较的事情,重温了一下编辑距离算法及其应用。
一、概念:
编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。
许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
例如将kitten一字转成sitting:
- kitten(k→s)
- sitten(e→i)
- sittin(+g)
- sitting
俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。
二、算法:
问题:找出字符串的编辑距离,即把一个字符串s1最少经过多少步操作变成编程字符串s2,操作有三种,添加一个字符,删除一个字符,修改一个字符
解析:首先定义这样一个函数(矩阵)——d(i, j),它表示第一个字符串的长度为i的子串到第二个字符串的长度为j的子串的编辑距离。
算法过程:
- str1或str2的长度为0返回另一个字符串的长度。 if(str1.length==0) return str2.length; if(str2.length==0) return str1.length;
- 初始化(n+1)*(m+1)的矩阵d,并让第一行和列的值从0开始增长;
- 扫描两字符串(n*m级的),如果:str1[i] == str2[j],用temp记录它,为0。否则temp记为1。然后在矩阵d[i,j]赋于d[i-1,j]+1 、d[i,j-1]+1、d[i-1,j-1]+temp三者的最小值;
- 扫描完后,返回矩阵的最后一个值d[n][m]即是它们的距离。
计算相似度公式:1-它们的距离/两个字符串长度的最大值。
通过算法过程伪码我们不难发现,算法其实是一种动态规划。因此理论上使用暴力算法一遍一遍地扫描字符串也可以解出,但逻辑过于复杂,动态规划思想很好地解决了这个问题。
三、代码实现
package tools;
public class EditDistance {
private int[][] array;
private String str1;
private String str2;
public EditDistance(String str1, String str2) {
this.str1 = str1;
this.str2 = str2;
}
public int edit() {
int max1 = str1.length();
int max2 = str2.length();
// 建立数组,比字符长度大一个空间
array = new int[max2 + 1][max1 + 1];
for (int i = 0; i <= max1; i++) {
array[0][i] = i;
}
for (int j = 0; j <= max2; j++) {
array[j][0] = j;
}
for (int i = 1; i <= max1; i++) {
for (int j = 1; j <= max2; j++) {
array[j][i] = levenshtein(i, j, str1.charAt(i - 1), str2.charAt(j - 1));
}
}
return array[max2][max1];
}
public int levenshtein(int i, int j, char si, char sj) {
int result = 0;
if (i >= 1 && j >= 1) {
int a = array[j - 1][i] + 1;
int b = array[j][i - 1] + 1;
int c = array[j - 1][i - 1] + ((si != sj) ? 2 : 0);
result = min(a, b, c);
}
return result;
}
public int min(int a, int b, int c) {
int temp = a < b ? a : b;
return temp < c ? temp : c;
}
// 计算相似度
public float similarity() {
float similarity = 1 - (float) array[str2.length()][str1.length()] / Math.max(str1.length(), str2.length());
return similarity;
}
public static void main(String args[]) {
String str1 = "首选的确诊方法是()";
String str2 = "首选确诊方法是";
EditDistance lt = new EditDistance(Tools.cleanTitle(str1), Tools.cleanTitle(str2));
System.out.println(lt.edit());
System.out.println(lt.similarity());
}
}
测试结果:
3
0.7
四、应用
- DNA分析
- 拼字检查
- 语音辨识
- 抄袭侦测
- 搜索引擎