您的位置:首页 > 编程语言 > C语言/C++

编辑距离 算法详述计算两个字符串差异 c++代码

2016-08-31 10:30 639 查看
题目描述:

要求两字符串有差异的字符个数。例如: 

aaaaabaaaaa 

aaaaacaabaa 

这两个字符串,最大公共字串长度是5,但它们只有两个字符不同,函数输出值应为2。 

如果是: 

aaabbbcccddd 

aaaeeeddd 

函数的输出值应该是6。 

比较形象地形容一下,把两个字符串排成上下两行,每个字符串都可以在任何位置插入空格以便上下对齐,每个列上至少有一个字符来自这两个字符串。当对齐程度最高的时候,没有对上的列的数即为函数输出值。 

aaabbbcccddd 

aaaeeeddd 

最优对齐状态是: 

aaabbbcccddd 

aaaeee     ddd 

没有对上的列是6,函数输出值为6。 

如果是: 

abcde 

acefg 

最优对齐状态是: 

abcde 

a  c  efg 

没有对上的列数是4,函数输出值为4。

 

 

问题抽象归类:(编辑距离问题)

设A和B是2个字符串。要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括:

(1)删除一个字符;

(2)插入一个字符;

(3)将一个字符改为另一个字符。

将字符串A变换为字符串B所用的最少字符操作数称为字符串A到B的编辑距离,记为d(A,B)。试设计一个有效算法,对任给的2个字符串A和B,计算出它们的编辑距离d(A,B)。

要求:

输入:第1行是字符串A,第2行是字符串B。

输出:字符串A和B的编辑距离d(A,B)

 

 

思路:动态规划

开一个二维数组d[i][j]来记录a0-ai与b0-bj之间的编辑距离,要递推时,需要考虑对其中一个字符串的删除操作、插入操作和替换操作分别花费的开销,从中找出一个最小的开销即为所求

具体算法:

首先给定第一行和第一列,然后,每个值d[i,j]这样计算:d[i][j]   =   min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+(s1[i]  ==  s2[j]?0:1));   

 最后一行,最后一列的那个值就是最小编辑距离

Levenshtein Distance (编辑距离) 算法详解

编辑距离即从一个字符串变换到另一个字符串所需要的最少变化操作步骤(以字符为单位,如son到sun,s不用变,将o->s,n不用变,故操作步骤为1)。

为了得到编辑距离,我们画一张二维表来理解,以beauty和batyu为例:



 

图示如1单元格位置即是两个单词的第一个字符比较得到的值,其值由它上方的值(1)、它左方的值(1)和、它左上角的值(0)来决定。当单元格所在的行和列所对应的字符(如[b]3对应的是a和b)相等时,它左上角的值+0,否则加1(如在1处,=[b]故左上角的值加0即0+0=0,而在[b]2!=[b] 故左上角的值加1即1+1=2)。然后再将单元格的左单元格和上单元格的值分别加1,(,然后取相加后得到的三个结果的最小值作为该单元的值如[b]1处相加后其左上、左、上的值为(0,2,2),故1单元格的值为0,而在3处,得到的值为(2,3,1),故3
c2f4
单元格的值为1)。

算法证明
这个算法计算的是将s[1…i]转换为t[1…j](例如将beauty转换为batyu)所需最少的操作数(也就是所谓的编辑距离),这个操作数被保存在d[i,j](d代表的就是上图所示的二维数组)中。
在第一行与第一列肯定是正确的,这也很好理解,例如我们将beauty转换为空字符串,我们需要进行的操作数为beauty的长度(所进行的操作为将beauty所有的字符丢弃)。
我们对字符可能进行的操作有三种:
将s[1…n]转换为t[1…m]当然需要将所有的s转换为所有的t,所以,d[n,m](表格的右下角)就是我们所需的结果。
如果我们可以使用k个操作数把s[1…i]转换为t[1…j-1],我们只需要把t[j]加在最后面就能将s[1…i]转换为t[1…j],操作数为k+1
如果我们可以使用k个操作数把s[1…i-1]转换为t[1…j],我们只需要把s[i]从最后删除就可以完成转换,操作数为k+1
如果我们可以使用k个操作数把s[1…i-1]转换为t[1…j-1],我们只需要在需要的情况下(s[i] != t[j])把s[i]替换为t[j],所需的操作数为k+cost(cost代表是否需要转换,如果s[i]==t[j],则cost为0,否则为1)。
这个证明过程只能证明我们可以得到结果,但并没有证明结果是最小的(即我们得到的是最少的转换步骤)。所以我们引进了另外一个算法,即d[i,j]保存的是上述三种操作中操作数最小的一种。这就保证了我们获得的结果是最小的操作数
 
可能进行的改进
现在的算法复杂度为O(mn),可以将其改进为O(m)。因为这个算法只需要上一行和当前行被存储下来就可以了。
如果需要重现转换步骤,我们可以把每一步的位置和所进行的操作保存下来,进行重现。
如果我们只需要比较转换步骤是否小于一个特定常数k,那么只计算高宽宽为2k+1的矩形就可以了,这样的话,算法复杂度可简化为O(kl),l代表参加对比的最短string的长度。
我们可以对三种操作(添加,删除,替换)给予不同的权值(当前算法均假设为1,我们可以设添加为1,删除为0,替换为2之类的),来细化我们的对比。
如果我们将第一行的所有cell初始化为0,则此算法可以用作模糊字符查询。我们可以得到最匹配此字符串的字符串的最后一个字符的位置(index number),如果我们需要此字符串的起始位置,我们则需要存储各个操作的步骤,然后通过算法计算出字符串的起始位置。
这个算法不支持并行计算,在处理超大字符串的时候会无法利用到并行计算的好处。但我们也可以并行的计算cost values(两个相同位置的字符是否相等),然后通过此算法来进行整体计算。
如果只检查对角线而不是检查整行,并且使用延迟验证(lazy evaluation),此算法的时间复杂度可优化为O(m(1+d))(d代表结果)。这在两个字符串非常相似的情况下可以使对比速度速度大为增加。、

#include <stdio.h>     
#include <string.h>     
char s1[1000],s2[1000];     
int min(int a,int b,int c) {     
    int t = a < b ? a : b;     
    return t < c ? t : c;     
}     
void editDistance(int len1,int len2)  
{     
    int** d=new int*[len1+1];  
    for(int k=0;k<=len1;k++)  
        d[k]=new int[len2+1];    
    int i,j;     
    for(i = 0;i <= len1;i++)     
        d[i][0] = i;     
    for(j = 0;j <= len2;j++)     
        d[0][j] = j;     
    for(i = 1;i <= len1;i++)     
        for(j = 1;j <= len2;j++)  
        {     
            int cost = s1[i] == s2[j] ? 0 : 1;     
            int deletion = d[i-1][j] + 1;     
            int insertion = d[i][j-1] + 1;     
            int substitution = d[i-1][j-1] + cost;     
            d[i][j] = min(deletion,insertion,substitution);     
        }     
        printf("%d\n",d[len1][len2]);   
        for(int k=0;i<=len1;k++)  
            delete[] d[k];  
        delete[] d;  
}     
int main()  
{     
    while(scanf("%s %s",s1,s2) != EOF)     
        editDistance(strlen(s1),strlen(s2));     
}   
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐