zoukankan      html  css  js  c++  java
  • 文本比较算法:编辑距离

    编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

    例如将kitten一字转成sitting:
    sitten (k→s)
    sittin (e→i)
    sitting (→g)

    俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。

    问题:找出字符串的编辑距离,即把一个字符串s1最少经过多少步操作变成编程字符串s2,操作有三种,添加一个字符,删除一个字符,修改一个字符。

    第一种解决方法,递归迭代。A和B字符距离的比较只有三种情况:A添加字符(B的比较位置+1)、A删除字符(A的比较位置+1)、A替换字符(A和B的比较位置均+1)。程序如下:

    /*
     *侯凯,2014-9-15
     *功能:LD距离
     */
    #include<iostream>
    using namespace std;
    
    int CalTheDistance(int spos1,int spos2,int len1,int len2,char* a,char* b)
    {
        if(spos1 >=len1)
        {
            if(spos2>=len2)return 0;
            else return (len2-spos2);
        }
        if(spos2>=len2)
        {
            return len1-spos1;
        }
        if(a[spos1]==b[spos2])
        {
            return CalTheDistance(spos1+1,spos2+1,len1,len2,a,b);
        }
        else
        {
            int d1 = CalTheDistance(spos1+1,spos2,len1,len2,a,b);
            int d2 = CalTheDistance(spos1,spos2+1,len1,len2,a,b);
            int d3 = CalTheDistance(spos1+1,spos2+1,len1,len2,a,b);
            return min(d1,min(d2,d3))+1;
        }
    }
    
    int main()
    {
        char str1[] = "abe";
        char str2[] = "acb";
        //聚类为2
        int distance = CalTheDistance(0,0,strlen(str1),strlen(str2),str1,str2);
        cout<<distance<<endl;
        system("Pause");
    }

    采用递归算法,只是理论上有效,便于理解,实际应用中会出现各种限制。一般,堆栈深度程序是设限制的。
    第二种方法,动态规划的思想。首先定义这样一个函数——edit(i, j),它表示第一个字符串的长度为i的子串到第二个字符串的长度为j的子串的编辑距离。显然有:

    if i == 0 且 j == 0,edit(i, j) = 0
    if i == 0 且 j > 0,edit(i, j) = j
    if i > 0 且j == 0,edit(i, j) = i

    示例:比较的两个字符串为“abcd”和“bedf”,阶段与状态(i,j)矩阵:

    image

    计算下一行得到:

    image

    进而:

    image

    可得状态转移方程:若A(i)=B(j),则LD(i,j)=LD(i-1,j-1);否则LD(i,j)=min{LD(i-1,j-1),min(i-1,j),min(i,j-1)}+1。这个关系式可以从题目中直接推到得到。最终得到:

    image

    程序实现如下:

    /*
     *侯凯,2014-9-15
     *功能:LD距离
     */
    #include<iostream>
    using namespace std;
    
    int CalTheDistance(string A,string B)
    {
        int **ptr = new int*[ A.size()+ 1];
        for(int i = 0; i < A.size() + 1 ;i++)
        {
            ptr[i] = new int[B.size() + 1];
        }
    
        for(int i=0;i<A.size()+1;i++)
        {
            ptr[i][0] = i;
        }
        for(int i=0;i<B.size()+1;i++)
        {
            ptr[0][i] = i;
        }
        for(int i=0;i<A.size();i++)
        {
            for(int j=0;j<B.size();j++)
            {
                if(A[i]==B[j])
                    ptr[i+1][j+1]=ptr[i][j];
                else
                    ptr[i+1][j+1]=min(ptr[i][j],min(ptr[i+1][j],ptr[i][j+1]))+1;
            }
        }
        int result = ptr[A.size()][B.size()];
        for(int i = 0; i < A.size() + 1 ;i++)
        {
            delete [] ptr[i];
            ptr[i] = NULL;
        }
        delete[] ptr;
        ptr = NULL;
        return result;
    }
    
    int main()
    {
        string str1 = "abcd";
        string str2 = "bedf";
        //聚类为3
        int distance = CalTheDistance(str1,str2);
        cout<<distance<<endl;
        system("Pause");
    }

    此时时间复杂度为O(mn),空间复杂度亦为O(mn),可对程序进一步改进,使空间复杂度降低为O(m),如下:

    /*
     *侯凯,2014-9-15
     *功能:LD距离
     */
    #include<iostream>
    using namespace std;
    
    int CalTheDistance(string A,string B)
    {
        int *ptr = new int[ B.size()+ 1];
    
        for(int i=0;i<B.size()+1;i++)
        {
            ptr[i] = i;
        }
    
        for(int i=0;i<A.size();i++)
        {
            int tmp1 = ptr[0];
            ptr[0] += 1;
            for(int j=0;j<B.size();j++)
            {
                int tmp2 = tmp1;
                tmp1 = ptr[j+1];
                if(A[i]==B[j])
                    ptr[j+1]=tmp2;
                else
                    ptr[j+1]=min(ptr[j],min(tmp2,tmp1))+1;
            }
        }
        int result = ptr[B.size()];
        delete[] ptr;
        ptr = NULL;
        return result;
    }
    
    int main()
    {
        string str1 = "abcd";
        string str2 = "bedf";
        //聚类为3
        int distance = CalTheDistance(str1,str2);
        cout<<distance<<endl;
        system("Pause");
    }

    通过二维矩阵,我们不但可以得到两个字符串的编辑距离,也可以回溯得到匹配子串。

    以上面为例A=abcd,B=bedf,LD(A,B)=3

    他们的匹配为:

    A:abcd_

    B:_bedf

    如上面所示,蓝色表示完全匹配,黑色表示编辑操作,_表示插入字符或者是删除字符操作。如上面所示,黑色字符有3个,表示编辑距离为3。

    image

    从右下角单元格回溯,若Ai=Bj,则回溯到左上角单元格;若ai≠bj,回溯到左上角、上边、左边中值最小的单元格,若有相同最小值的单元格,优先级按照左上角、上边、左边的顺序。
    若回溯到左上角单元格,将Ai添加到匹配字串A,将Bj添加到匹配字串B;若回溯到上边单元格,将Bi添加到匹配字串B,将_添加到匹配字串A;若回溯到左边单元格,将_添加到匹配字串B,将Aj添加到匹配字串A;搜索晚整个匹配路径,匹配字串也就完成了。

    在比较长字符串的时候,还有其他性能更好的算法。留待后文详述。

  • 相关阅读:
    WPF之感触
    C# WinForm 给DataTable中指定位置添加列
    MyEclipse 8.6 download 官方下载地址
    将博客搬至CSDN
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
    Building Microservices with Spring Cloud
  • 原文地址:https://www.cnblogs.com/houkai/p/3972712.html
Copyright © 2011-2022 走看看