Levenshtein Distance + LCS 算法计算两个字符串的相似度

 【园里很多前辈写过关于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算法的时候,精简了一部分代码,引发一些计算错误,使结果不正确,已改正;感谢 混沌世界 指出;

posted @ 2013-06-06 10:52  三度空间  阅读(4462)  评论(15编辑  收藏  举报