zoukankan      html  css  js  c++  java
  • 字符串编辑距离问题详解

      字符串的编辑距离也被称为距Levenshtein距离(Levenshtein Distance),属于经典算法,常用方法使用递归,更好的方法是使用动态规划算法,以避免出现重叠子问题的反复计算,减少系统开销。

    《编程之美》一书中3.3节中计算两个字符串的相似度,归根到底也是要求两个字符串的距离,其中问题是这样提出的:

      许多程序会大量使用字符串。对于不同的字符串,我们希望能够有办法判断其相似程序。我们定义一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:

    •         修改一个字符(如把"a"替换为"b");
    •         增加一个字符(如把"abdd"变为"aebdd");
    •         删除一个字符(如把"travelling"变为"traveling");

        比如,对于"abcdefg"和"abcdef"两个字符串来说,我们认为可以通过增加/减少一个"g"的方式来达到目的。上面的两种方案,都仅需要一 次 。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于"距离+1"的倒数。也就是说,"abcdefg"和"abcdef"的距离为1,相似度 为1/2=0.5。给定任意两个字符串,你是否能写出一个算法来计算它们的相似度呢?    

      其实这个问题的关键是要求两个字符串的编辑距离

    例如 将kitten一字转成sitting:

    1. sitten (k→s)
    2. sittin (e→i)
    3. sitting (→g)

    俄罗斯科学家Vladimir Levenshtein1965年提出这个概念。

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

     

    下面我们就针对这个问题来详细阐述一下:

    我们假定函数dist(str1, str2)表示字串str1转变到字串str2的编辑距离,那么对于下面3种极端情况,我们很容易给出解答(0表示空串)。

    • dist(0, 0) = 0
    • dist(0, s) = strlen(s)
    • dist(s, 0) = strlen(s)

    对于一般的情况,dist(str1, str2)我们应该如何求解呢?

    假定我们现在正在求解dist(str1+char1, str2+char2),也就是把"str1+char1"转变成"str2+char2"。在这个转变过称中,我们要分情况讨论:

    1. str1可以直接转变成str2。这时我们只要把char1转成char2就可以了(如果char1 != char2)。
    2. str1+char1可以直接转变成str2。这时我们处理的方式是插入char2
    3. str1可以直接转成str2+char2。这时的情况是我们需要删除char1

      综合上面三种情况,dist(str1+char1, str2+char2)应该是三者的最小值。

    解析:

    首先定义这样一个函数——edit(i, j),它表示第一个字符串的长度为i的子串到第二个字符串的长度为j的子串的编辑距离。

    显然可以有如下动态规划公式:

    • if i == 0 j == 0edit(i, j) = 0
    • if i == 0 j > 0edit(i, j) = j
    • if i > 0 j == 0edit(i, j) = i
    • if i ≥ 1   j ≥ 1 edit(i, j) == min{ edit(i-1, j) + 1, edit(i, j-1) + 1, edit(i-1, j-1) + f(i, j) },当第一个字符串的第i个字符不等于第二个字符串的第j个字符时,f(i, j) = 1;否则,f(i, j) = 0

    我们建立以下表格,将两个字符串按照表格1所示的样子进行摆放,规则按照以上公式进行输入,如下所示,我们可以得到每个表格中的值,如下表格2所示:

     

    0

    a

    b

    c

    d

    e

    f

    0

      

      

      

      

      

      

      

    a

      

      

      

      

      

      

      

    c

      

      

      

      

      

      

      

    e

      

      

      

      

      

      

      

               表格1(字符串摆放表格)

     

    0

    a

    b

    c

    d

    e

    f

    0

     0

    2

    3

     4

     5

     6

    a

     1

      

      

      

      

      

      

    c

     2

      

      

      

      

      

      

    e

     3

      

      

      

      

      

      

          表格2(按照规则计算i==0 或 j==0的情况)

     计算edit(1, 1)edit(0, 1) + 1 == 2edit(1, 0) + 1 == 2edit(0, 0) + f(1, 1) == 0 + 1 == 1min(edit(0, 1)edit(1, 0)edit(0, 0) + f(1, 1))==1,因此edit(1, 1) == 1依次类推,有如下表格3所示最终的矩阵:

     

    0

    a

    b

    c

    d

    e

    f

    0

     0

     1

     2

     3

     4

     5

     6

    a

     1

     0

     1

     2

     3

     4

     5

    c

     2

     1

     1

     1

     2

     3

     4

    e

     3

     2

     2

     2

     2

     2

     3

          表格3(最终计算得到的字符串相对距离)

    此时右下角即为我们所需要的两个字符串的编辑距离。即字符串 "abcdef""ace"的编辑距离为3.

    有了以上的步骤,相信大家已经很清楚了,使用动态规划算法的时候,需要建立子问题的表格,以上的表格就是。而且我们能够很容易的使用二维数组建立。代码实现也就易如反掌了!

    以下是我的实现过程,希望对大家有用,如果有什么可以优化或者错误的地方,希望能够得到批评指正。

     

      1 #include <iostream>
      2 #include <string>
      3 
      4 using namespace  std;
      5 
      6 int min3Value(int a, int b, int c)
      7 {
      8     int tmp = (a <= b? a:b);
      9     return (tmp<=c? tmp: c);
     10 }
     11 
     12 
     13 int Get2StringEditDis(string strA, string strB)
     14 {
     15     int nLenA = strA.length();
     16     int nLenB = strB.length();
     17     int **matrix = new int *[nLenA + 1];
     18     for (int i = 0; i != nLenA +1; i++)
     19     {
     20         matrix[i] = new int[nLenB + 1];
     21     }
     22     // 动态规划 计算
     23     // 初始化数组
     24     matrix[0][0] = 0;
     25     int p,q; 
     26     // j = 0; edit(i, j) = i
     27     for (p = 1; p!= nLenA+1; p++)
     28     {
     29         matrix[p][0] = p;
     30     }
     31     // i = 0; edit(i,j) = j
     32     for (q=1; q != nLenB+1; q++)
     33     {
     34         matrix[0][q] = q;
     35     }
     36     // i>0, j>0
     37     for (int j = 1; j != nLenA+1; j++)
     38     {
     39         for (int k = 1; k !=  nLenB+1; k++)
     40         {
     41             int Fjk = 0;
     42             if (strA[j-1] != strB[k-1])
     43             {
     44                 Fjk = 1;
     45             }
     46             matrix[j][k] = min3Value(matrix[j-1][k]+1,matrix[j][k-1]+1,matrix[j-1][k-1]+Fjk);
     47         }
     48     }
     49     
     50 
     51 
     52 
     53     // 输出距离矩阵
     54     // 第一行输出字符串b
     55     // 第一列输出字符串A
     56     cout<<"*****************************"<<endl;
     57     cout<<"字符串编辑距离矩阵如下:
    ";
     58     for (p = -1; p!= nLenA +1; p++)
     59     {
     60         for (q = -1; q !=nLenB+1; q++)
     61         {
     62             //cout.width(3),cout<<matrix[p][q];
     63             cout.width(3);
     64             if (p ==-1 && q == -1)
     65             {
     66                 cout<<" ";
     67             }
     68             else if (p + q == -1)
     69             {
     70                 cout<<"NUL";
     71             }
     72             else if (p == -1 && q >0)
     73             {
     74                 cout<<strB[q-1];
     75             }
     76             else if(q == -1 && p > 0)
     77             {
     78                 cout<<strA[p-1];
     79             }
     80             else
     81             {
     82                 cout<<matrix[p][q];
     83             }
     84         }
     85         cout<<endl;
     86     }
     87     cout<<"*****************************"<<endl;
     88     //
     89     int  nEditDis = matrix[nLenA][nLenB];
     90     for (int m = 0; m!=nLenA + 1; m++)
     91     {
     92         delete[] matrix[m];
     93     }
     94     delete[] matrix;
     95 
     96 
     97     return  nEditDis;
     98 }
     99 
    100 
    101 int main()
    102 {
    103     string strA("abcdefgh");
    104     string strB("adgcf");
    105 
    106     int nDist = Get2StringEditDis(strA,strB);
    107     cout<<"The edit dis is  "<<nDist<<endl;
    108 
    109     return 0;
    110 }

    结果如图1所示:

    其中对于另一篇随笔中有亚马逊今年的在线笔试题中,有一道该类型题目的变种~~~ 大家可以翻阅下!地址: http://www.cnblogs.com/jiabei521/p/3352935.html

  • 相关阅读:
    tomcat并发个题-未解决
    tengine安装
    nginx获得自定义参数
    nginx限流
    树形背包——hdu1561
    树形dp专题
    单调队列——P1725 琪露诺
    单调队列,dp——POJ
    记忆化搜索——HDU
    区间dp——POJ
  • 原文地址:https://www.cnblogs.com/jiabei521/p/3353390.html
Copyright © 2011-2022 走看看