【园里很多前辈写过关于Levenshtein Distance算法的文章,对算法原理有很深入的剖析讲解。我这里班门弄斧,尽我所能,将这个算法以更加通俗易懂的语言来阐述,有何纰漏,请指出和见谅】
Levenshtein Distance,编辑距离算法,是指从字符串A变成字符串B,所需的最少编辑(增,删,插入)次数。应用也相当广泛,这里我们用来求解两个字符串的相似度。
算法原理我就不再说明(注,对于算法原理,请参照 http://en.wikipedia.org/wiki/Levenshtein_distance ),这里只图解实现的过程。
【例子】假设现在有源串“jary”与目标串“jerry”,求源串到目标串的编辑距离。
图解过程如下:
step 1:初始化如下矩阵
step 2:从源串的第一个字符(“j”)开始,从上至下与目标串进行对比
如果两个字符相等,则在从此位置的左,上,左上三个位置中取出最小的值, 如果最小值在 左,上 这两个位置上,则加1,如果在左上上,则加0;若不等,则在从此位置的左,上,左上三个位置中取出最小的值再加上1;
第一次,源串第一个字符“j” 与目标串的“j”对比,左,上,左上三个位置中取出最小的值0,因为两字符相等,所以加上0;接着,依次对比“j”→“e”,“j”→“r”,“j”→“r”,,“j”→“y” 到扫描完目标串。
step 3:遍历整个源串与目标串对比:
step 4:扫描完最后一列,则最后一个为最短编辑距离:
求出编辑距离,那么两个字符串的相似度 Similarity = (Max(x,y) - Levenshtein)/Max(x,y),其中 x,y 为源串和目标串的长度。(计算公式修改)
LCS算法:用于求解两个字符串之间最长的公共子序列;
【例子】假如有“张则智”和“张则知”两个字符串,求解步骤如下:
step 1:初始化如下矩阵,然后,“张则智”三个字分别跟“张”字对比,相同的为1+上一步的结果(左对角),不同位0;
step 2:依次使用源串与目标串对比,这里第二步是,“张则智”跟“则”对比,相同为1+上一步的结果(左对角),不同为0
step 3:对比完整个矩阵,扫描矩阵中最大的数为最长公共子序列;
计算出编辑距离,最长公共子序列,然后用 万仓一黍 前辈提供的公式来计算:S(A,B)=LCS(A,B)/(LD(A,B)+LCS(A,B))
【注,在实际应用中,此公式会出现不足,感谢 万仓一黍 前辈指出,实际应用中,应该配合使用 LCS 算法;可参考 http://www.cnblogs.com/grenet/archive/2010/06/04/1751147.html 】
代码实现(使用C#来实现)
1. 计算编辑距离:
public static int LevenshteinDistance(string source, string target) { int cell = source.Length; int row = target.Length; if (cell == 0) { return row; } if (row == 0) { return cell; } int[, ] matrix = new int[row + 1, cell + 1]; for (var i = 0; i <= cell; i++) { matrix[0, i] = i; } for (var j = 1; j <= row; j++) { matrix[j, 0] = j; } var tmp = 0; for (var k = 0; k < row; k++) { for (var l = 0; l < cell; l++) { if (source[l].Equals(target[k])) tmp = 0; else tmp = 1; matrix[k + 1, l + 1] = Math.Min(Math.Min(matrix[k, l] + tmp, matrix[k + 1, l] + 1), matrix[k, l + 1] + 1); } } return matrix[row, cell]; }
2. LCS算法代码:
1 public static int LongestCommonSubsequence(string source, string target) 2 { 3 if (source.Length == 0 || target.Length == 0) 4 return 0; 5 int len = Math.Max(target.Length, source.Length); 6 int[, ] subsequence = new int[len + 1, len + 1]; 7 for (int i = 0; i < source.Length; i++) 8 { 9 for (int j = 0; j < target.Length; j++) 10 { 11 if (source[i].Equals(target[j])) 12 subsequence[i + 1, j + 1] = subsequence[i, j] + 1; 13 else 14 subsequence[i + 1, j + 1] = 0; 15 } 16 } 17 int maxSubquenceLenght = (from sq in subsequence.Cast < int > () select sq).Max < int > (); 18 return maxSubquenceLenght; 19 }
2. 计算两个字符串间的相识度:
1 public static float StringSimilarity(string source, string target) 2 { 3 var ld = LevenshteinDistance(source, target); 4 var maxLength = Math.Max(source.Length, target.Length); 5 return (float)(maxLength - ld) / maxLength; 6 }
3. 计算两个字符串的相似度:
1 public static float StringSimilarity(string source, string target) 2 { 3 var ld = LevenshteinDistance(source, target); 4 var lcs = LongestCommonSubsequence(source, target); 5 return ((float)lcs)/(ld+lcs);; 6 }
【2013.06.07 修改】在写LD算法的时候,精简了一部分代码,引发一些计算错误,使结果不正确,已改正;感谢 混沌世界 指出;