优化前代码的代码来自于——路在脚下:计算字符串相似度算法——Levenshtein http://wdhdmx.iteye.com/blog/1343856
原创博文,也是博主的处女作,先谢过大家愿意花时间来看我唠叨了
~~~~~~~~~~~~~~~~~~~~华丽分割线~~~~~~~~~~~~~~~~~~~~~~~~~
非常建议学习Levenshtein之前先看看上面这边文章,通俗易懂。
以下是优化后的Levenshtein计算字符串相似度JAVA程序
(可直接运行)
public class MyLevenshtein {
static String str1;
static String str2;
public static void main(String[] args) {
// 要比较的两个字符串
str1 = "听说马上就要放假了";
str2 = "你听说要放假了";
getSimilar();
// System.out.println("计算次数:" + count);
}
public static void getSimilar() {
int len1 = str1.length();
int len2 = str2.length();
int[][] dif = new int[len1][len2];
for (int i = 0; i < len1; i++) {
for (int j = 0; j < len2; j++) {
dif[i][j] = 100;
}
}
int begi = 0;
int begj = 0;
int endi = len1 - 1;
int endj = len2 - 1;
int n = 1;
int i = 0;
int j = 0;
int middle = 0;
int similar_i;
int similar_j;
int diff = 0;
int count = 0;
getValue2(dif, 0, 0, 1);
getValue2(dif, 1, 0, 1);
getValue2(dif, 0, 1, 1);
getValue2(dif, endi, endj, -1);
getValue2(dif, endi - 1, endj, -1);
getValue2(dif, endi, endj - 1, -1);
System.out.println("point:" + 0 + " " + 0);
System.out.println("dif:" + dif[0][0]);
System.out.println("point:" + endi + " " + endj);
System.out.println("dif:" + dif[endi][endj]);
while (begi < endi && begj < endj) {
if (n == 1) {
if (count > 0) {
endi = i;
endj = j;
}
i = begi;
j = begj;
} else if (n == -1) {
if (count > 0) {
begi = i;
begj = j;
}
i = endi;
j = endj;
middle += 1;
}
do {
middle = dif[i][j];
similar_i = dif[i][j + n];
similar_j = dif[i + n][j];
if (middle < Math.min(similar_i, similar_j)) {
i += n;
j += n;
getValue2(dif, i, j, n);
} else if (middle >= similar_i) {
j += n;
} else if (middle >= similar_j) {
i += n;
}
System.out.println("point:" + i + " " + j);
System.out.println("dif:" + dif[i][j]);
System.out.println("middle:" + middle);
System.out.println("n:" + n);
if (i < len1 && j < len2) {
getValue2(dif, i + n, j, n);
getValue2(dif, i, j + n, n);
}
} while (dif[i][j] <= middle);
n *= -1;
count++;
}
if (begi == endi) {
diff = dif[begi][begj] + dif[endi][endj] + (endj - endi);
} else {
diff = dif[begi][begj] + dif[endi][endj] + (begj - begi);
}
System.out.println("差异步骤:" + diff);
float similarity = 1 - (float) diff / Math.max(len1, len2);
System.out.println("相似度:" + similarity);
}
public static void getValue2(int[][] dif, int i, int j, int n) {
if (i < dif.length && j < dif[0].length) {
int temp;
if (str1.charAt(i) == str2.charAt(j)) {
temp = 0;
} else {
temp = 1;
}
if ((i == dif.length - 1 && j == dif[0].length - 1) || (i == 0 && j == 0)) {
dif[i][j] = temp;
} else if (i == dif.length - 1 || i == 0) {
dif[i][j] = dif[i][j - n] + temp;
} else if (j == dif[0].length - 1 || j == 0) {
dif[i][j] = dif[i - n][j] + temp;
} else {
dif[i][j] = min(dif[i - n][j - n] + temp, dif[i][j - n] + 1, dif[i - n][j] + 1);
}
// count++;
}
}
// 得到最小值
private static int min(int... is) {
int min = Integer.MAX_VALUE;
for (int i : is) {
if (min > i) {
min = i;
}
}
return min;
}
}
运行完的结果为
point: i j
dif: dif ( i )( j )
middle: X
n: -1 / 1
差异步骤: 4
相似度:0.5555556
Levenshtein算法可用于快速对比两个字符串之间的差异(增删改都是一视同仁的操作,差异度+1)
相似度的计算公式为 :
1-差异度/最大字符串长度
Levenshtein算法矩阵如下,数字是差异步骤
Tables | 听 | 说 | 马 | 上 | 就 | 要 | 放 | 假 | 了 |
---|---|---|---|---|---|---|---|---|---|
你 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
听 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
说 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
要 | 3 | 2 | 2 | 3 | 4 | 4 | 5 | 6 | 7 |
放 | 4 | 3 | 3 | 3 | 4 | 5 | 4 | 5 | 6 |
假 | 5 | 4 | 4 | 4 | 4 | 5 | 5 | 4 | 6 |
了 | 6 | 5 | 5 | 5 | 5 | 5 | 6 | 5 | 4 |
原来的Levenshtein算法要把整个矩阵算出来,用两层for循环嵌套,但细心一点就会发现只有中间加粗的数字是有效可累加的差异步骤,每一个格子又按照其右上的三个数中的最小数和当前位置的字符是否相等来决定。
所以我们可以得出,其他的格子的计算和记录都是无用功!!
只有加黑的自身与其周围的格子有用,所以我们优化一下算法,重新得到的计算差异步骤表格如下:
Tables | 听 | 说 | 马 | 上 | 就 | 要 | 放 | 假 | 了 |
---|---|---|---|---|---|---|---|---|---|
你 | 1 | 2 | |||||||
听 | 1 | 2 | 2 | 2 | |||||
说 | 2 | 1 | 2 | 2 | 1 | 1 | |||
要 | 2 | 2 | 3 | 1 | 0 | 1 | |||
放 | 1 | 0 | 1 | ||||||
假 | 1 | 0 | 1 | ||||||
了 | 1 | 0 |
优化后的算法只要算上面不是空格的格子即可,而中间的2+2正是等于4,这个就是我们要的差异步骤,是不是有点搞不懂,我们来慢慢解析。
这个算法的优化思路有点像快速排序,从最前面(0,0)的格子开始遍历,直到差异步骤增加1,马上改为从最后面(len1,len2)开始遍历,同样的直到差异步骤增加1,然后又重新从上上步的结束位置开始遍历(即从前往后的顺序)……如此,直到从后往前的横或纵坐标即将超过从前往后的横或纵坐标。
时间仓促,没来得及加注释,向各位看文的客官抱拳了。
PS. 这段代码逻辑挺难理清的,匆忙写完之后仅用了几个数据检验,不排除有没排查到的错误,再次抱拳。