• 文本比较算法:编辑距离


    编辑距离,又称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;搜索晚整个匹配路径,匹配字串也就完成了。

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

  • 相关阅读:
    实战篇之实现 OutLook 中以 EDM 形式发送通知邮件
    ASP.NET MVC5 之路由器
    ASP.NET MVC5 之数据迁移
    说不出的烦
    ASP.NET MVC5 之 Log4Net 的学习和使用
    读取配置文件参数和文件路径
    序列化和反序列化示例
    面向对象之封装
    面向对象4之常用的乱七八糟
    面向对象三之继承和派生
  • 原文地址:https://www.cnblogs.com/houkai/p/3972712.html
Copyright © 2020-2023  润新知