• 计算字符串的相似度


    本人阅读了《编程之美》,参阅了其中的——计算字符串的相似度——一节。感觉颇为实用。现将这一文章贴于此处,并将代码赋予其后。

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

        1.修改一个字符(如把“a”替换为“b”)。

        2.增加一个字符(如把“abdd”变为“aebdd”)。

        3.删除一个字符(如把“travelling”变为“traveling”)。

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

      分析与解法

      不难看出,两个字符串的距离肯定不超过它们的长度之和(我们可以通过删除操作把两个串都转化为空串)。虽然这个结论对结果没有帮助,但至少可以知道,任意两个字符串的距离都是有限的。

      我们还是应该集中考虑如何才能把这个问题转化成规模较小的同样的问题。如果有两个串A=xabcdae和B=xfdfa,它们的第一个字符是相同的,只要计算A[2,…,7]=abcdae和B[2,…,5]=fdfa的距离就可以了。但是如果两个串的第一个字符不相同,那么可以进行如下的操作(lenA和lenB分别是A串和B串的长度):

        1.删除A串的第一个字符,然后计算A[2,…,lenA]和B[1,…,lenB]的距离。

        2.删除B串的第一个字符,然后计算A[1,…,lenA]和B[2,…,lenB]的距离。

        3.修改A串的第一个字符为B串的第一个字符,然后计算A[2,…,lenA]和B[2,…,lenB]的距离。

        4.修改B串的第一个字符为A串的第一个字符,然后计算A[2,…,lenA]和B[2,…,lenB]的距离。

        5.增加B串的第一个字符到A串的第一个字符之前,然后计算A[1,…,lenA]和B[2,…,lenB]的距离。

        6.增加A串的第一个字符到B串的第一个字符之前,然后计算A[2,…,lenA]和B[1,…,lenB]的距离。

      在这个题目中,我们并不在乎两个字符串变得相等之后的字符串是怎样的。所以,可以将上面6个操作合并为:

        1.一步操作之后,再将A[2,…,lenA]和B[1,…,lenB]变成相同字符串。

        2.一步操作之后,再将A[1,…,lenA]和B[2,…,lenB]变成相同字符串。

        3.一步操作之后,再将A[2,…,lenA]和B[2,…,lenB]变成相同字符串。

      这样,很快就可以完成一个递归程序。

      在以上面的思想完成代码后,对程序进行了一番测试。第一次找了两个相似的字符串,长度分别为15和17。速度和结果都比较满意。这也印证了算法的正确性。第二次找了两个相似的字符串,长度分别为1500和1507。嗯,直接跳出错误,说是堆栈错误。实际上是由于递归嵌套出了问题。采用递归算法,只是理论上有效,便于理解,实际应用中会出现各种限制。如本例,嵌套约1000层的时候就超过了系统的限制。必须想一个解决之道。仔细观察,可以发现用数学性的语言描述就是

      F(n,m)=G(F(n,m),F(n+1,m),F(n,m+1))

      这个可以简化为递推,由于递推可以放在一个函数内,就解决了系统的递归限制。

      再新代码完成之后,照例还是对代码测试了一番。还是用两个相似的字符串,长度分别为1500和1507,结果能出来,但是效率差了点。在笔者的电脑上用了6秒中左右。仅仅是比较文本,就要6秒钟,比较难以接受,而且从代码看时间复杂度和空间复杂度都是O(n2)。

      必须得改进!!!

      在看了代码之后,发现代码运行速度慢可能出现在两个地方。一个是mDic对象,用的是Dictionary对象,在运行中反复读取和存储可能会影响速度,如果改为用数组可能效果会好点。哪位对这个有研究的同道,望不吝赐教。一个是String对象的Chars(Index)的方法。可能在每次执行到这一步时,会先把字符串转化为字符数组再返回一个字符,或者是遍历这个字符串,返回一个字符。对于本例中,大约需要执行1500×1500次,等于反复遍历,时间就浪费了。建议一开始就转化为字符数组,等到比较时就不需要遍历或转化了。

      按照这两个思路对代码进行了修改,然后测试。效果很满意,本例测试几乎就是一瞬间。

      程序完成之后,经测试,结果和速度都令人满意,稍显美中不足的是就是空间复杂度还是比较高,为O(S1×S2),当S1和S2都比较大的时候,可能会占用非常多的空间。

      如何解决这个问题呢?

      经过对计算过程的分析,我发现作为存储的二维矩阵,在每一个循环中,其实只有一行的数据参与了计算,之前的数据行就不再参与计算了。因此,从这个出发点入手,对代码进行了微调,将二维数组改为一维数组。经测试,结果和速度与之前思索之三中的代码没有差异。但空间复杂度少了很多,为O(S1)。

      现将代码赋予其后,用的是VB2005

     

     

     

     1 Public Class clsDistance
     2     Private mCharA() As Char
     3     Private mCharB() As Char
     4     Private mCharALen As Integer
     5     Private mCharBLen As Integer
     6
     7     Public Sub New(ByVal StrA As String, ByVal StrB As String)
     8
     9         mCharA = StrA.ToCharArray
    10         mCharB = StrB.ToCharArray
    11         mCharALen = mCharA.Length
    12         mCharBLen = mCharB.Length
    13
    14     End Sub
    15
    16     Public Function CacuDistance() As Integer
    17         Dim i As Integer
    18
    19         If mCharALen = 0 Then Return mCharBLen
    20         If mCharBLen = 0 Then Return mCharALen
    21
    22         Dim j As Integer = Min(mCharALen, mCharBLen) - 1
    23         Dim tP1 As Integer, tP2 As Integer
    24
    25         tP1 = -1
    26         tP2 = -1
    27
    28         For i = 0 To j
    29             If mCharA(i) <> mCharB(i) Then
    30                 tP1 = i
    31                 Exit For
    32             End If
    33         Next
    34
    35         If tP1 = -1 Then Return Math.Abs(mCharALen - mCharBLen)
    36
    37         For i = 0 To j - tP1
    38             If mCharA(mCharALen - i - 1) <> mCharB(mCharBLen - i - 1) Then
    39                 tP2 = i
    40                 Exit For
    41             End If
    42         Next
    43
    44         If tP2 = -1 Then Return Math.Abs(mCharALen - mCharBLen)
    45
    46         Dim tA(mCharALen - tP1 - tP2) As Integer
    47
    48         For i = 0 To tA.GetUpperBound(0)
    49             tA(i) = i
    50         Next
    51
    52         Dim tN1 As Integer, tN2 As Integer, tN3 As Integer
    53
    54         For i = 0 To mCharBLen - tP1 - tP2 - 1
    55             tN1 = tA(0)
    56             tN2 = tN1 + 1
    57             For j = 1 To tA.GetUpperBound(0)
    58                 If mCharA(mCharALen - tP2 - j) =  _

                  mCharB(mCharBLen - tP2 - i - 1) Then
    59                     tN3 = tN1
    60                 Else
    61                     tN3 = Min(tA(j), tN1, tN2) + 1
    62                 End If
    63                 tA(j - 1) = tN2
    64                 tN2 = tN3
    65                 tN1 = tA(j)
    66             Next
    67             tA(tA.GetUpperBound(0)) = tN2
    68         Next
    69
    70         Return tA(tA.GetUpperBound(0))
    71
    72     End Function
    73
    74     Public Function Min(ByVal ParamArray Num() As Integer) As Integer
    75         Dim tN As Integer, i As Integer
    76         If Num.Length = 0 Then Return Nothing
    77         tN = Num(0)
    78
    79         For i = 1 To Num.GetUpperBound(0)
    80             If Num(i) < tN Then tN = Num(i)
    81         Next
    82
    83         Return tN
    84     End Function
    85
    86  End Class

    在看完《编程之美》一书的“计算字符串的相似度”一文后,对该书最后提出的问题作一点回忆与思考。

     

    这里先将原问题再复述一遍:  

     

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

     

    1.修改一个字符(如把“a”替换为“b”);

     

    2.增加一个字符(如把“abdd”变为“aebdd”);

     

    3.删除一个字符(如把“travelling”变为“traveling”);

     

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

     

    给定任意两个字符串,你是否能写出一个算法来计算它们的相似度呢?

     

    原文的分析与解法  

     

    不难看出,两个字符串的距离肯定不超过它们的长度之和(我们可以通过删除操作把两个串都转化为空串)。虽然这个结论对结果没有帮助,但至少可以知道,任意两个字符串的距离都是有限的。

     

    我们还是就住集中考虑如何才能把这个问题转化成规模较小的同样的子问题。如果有两个串A=xabcdae和B=xfdfa,它们的第一个字符是相同的,只要计算A[2,...,7]=abcdae和B[2,...,5]=fdfa的距离就可以了。但是如果两个串的第一个字符不相同,那么可以进行如下的操作(lenA和lenB分别是A串和B串的长度)。

     

    1.删除A串的第一个字符,然后计算A[2,...,lenA]和B[1,...,lenB]的距离。

     

    2.删除B串的第一个字符,然后计算A[1,...,lenA]和B[2,...,lenB]的距离。

     

    3.修改A串的第一个字符为B串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离。

     

    4.修改B串的第一个字符为A串的第一个字符,然后计算A[2,...,lenA]和B[2,...,lenB]的距离。

     

    5.增加B串的第一个字符到A串的第一个字符之前,然后计算A[1,...,lenA]和B[2,...,lenB]的距离。

     

    6.增加A串的第一个字符到B串的第一个字符之前,然后计算A[2,...,lenA]和B[1,...,lenB]的距离。

     

    在这个题目中,我们并不在乎两个字符串变得相等之后的字符串是怎样的。所以,可以将上面的6个操作合并为:

     

    1.一步操作之后,再将A[2,...,lenA]和B[1,...,lenB]变成相字符串。

     

    2.一步操作之后,再将A[2,...,lenA]和B[2,...,lenB]变成相字符串。

     

    3.一步操作之后,再将A[1,...,lenA]和B[2,...,lenB]变成相字符串。

     

    这样,很快就可以完成一个递归程序。

    1 int calculateStringDistance(string strA, int pABegin, int pAEnd, string strB, int pBBegin, int pBEnd)

     2 {

     3     if(pABegin > pAEnd)

     4     {

     5         if(pBBegin > pBEnd)

     6             return 0;

     7         else

     8             return pBEnd - pBBegin + 1;

     9     }

    10

    11     if(pBBegin > pBEnd)

    12     {

    13         if(pABegin > pAEnd)

    14             return 0;

    15         else

    16             return pAEnd - pABegin + 1;

    17     }

    18

    19     if(strA[pABegin] == strB[pBBegin])

    20     {

    21         return calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);

    22     }

    23     else

    24     {

    25         int t1 = calculateStringDistance(strA, pABegin, pAEnd, strB, pBBegin+1, pBEnd);

    26         int t2 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin, pBEnd);

    27         int t3 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);

    28         return minValue(t1, t2, t3) + 1;

    29     }

    30 }

     

    上面的递归程序,有什么地方需要改进呢?问题在于:在递归的过程中,有些数据被重复计算了。

     

    我们知道适合采用动态规划方法的最优化问题中的两个要素:最优子结构和重叠子问题。另外,还有一种方法称为备忘录(memoization),可以充分利用重叠子问题的性质。

     

    下面简述一下动态规划的基本思想。和分治法一样,动态规划是通过组合子问题的解而解决整个问题的。我们知道,分治算法是指将问题划分 成一睦独立的子问题,递归 地求解各子问题,然后合并子问题的解而得到原问题的解。与此不同,动态规划适用于子问题不是独立 的情况,也就是各子问题包含公共的子子问题。在这种情况 下,若用分治法则会做许多不必要的工作,即重复地求解公共的子子问题。动态规划算法对每个子子问题只求解一次,将其结果保存在一张表中,从而避免每次遇到各个子问题时重新计算答案。

     

    动态规划通常应用于最优化问题。此类问题可能有很多种可行解,每个解有一个值,而我们希望找出一个具有最优(最大或最小)值的解。称这样的解为该问题的“一个”最优解(而不是“确定的”最优解),因为可能存在多个取最优值的解。

     

    动态规划算法的设计可以分为如下4个步骤:

     

    1)描述最优解的结构。

     

    2)递归定义最优解的值。

     

    3)按自底向上的方式计算最优解的值。

     

    4)由计算出的结果构造一个最优解。

     

    第1~3步构成问题的动态规划解的基础。第4步在只要求计算最优解的值时可以略去。如果的确做了第4步,则有时要在第3步的计算中记录一些附加信息,使构造一个最优解变得容易。

     

    该问题明显完全符合动态规划的两个要素,即最优子结构和重叠子问题特性。该问题的最优指的是两个字符串的最短距离,子问题的重叠性可以从原书中的那个递归算法中看出。

     

     

    本文来自编程入门网:http://www.bianceng.cn/Programming/sjjg/200910/11747_2.htm

    下面再来详细说说什么是重叠子问题。适用于动态规划求解的最优化问题必须具有的第二个要素是子问题的空间要“很小”,也就是用来解原问题的递归算法可以反复地解同样的子问题,而不是总在产生新的子问题。典型地,不同的子问题数是输入规模的一个多项式。当一个递归算法不断地调用同一问题时,我们说该最优问题包含重叠子问题。相反地,适合用分治法解决的问题只往往在递归的每一步都产生全新的问题。动态规划算法总是充分利用重叠子问题,即通过每个子问题只解一次,把解保存在一个需要时就可以查看的表中,而每次查表的时间为常数。

     

    根据以上的分析,我写了如下的动态规划算法:

     

    DP Algorithm

     

     1 /*

     2  * A loop method using dynamic programming.

     3  * Calculate from bottom to top.

     4  */

     5 int calculateStringDistance(string strA, string strB)

     6 {

     7     int lenA = (int)strA.length();

     8     int lenB = (int)strB.length();

     9     int c[lenA+1][lenB+1]; // Record the distance of all begin points of each string

    10

    11     // i: begin point of strA

    12     // j: begin point of strB

    13     for(int i = 0; i < lenA; i++) c[i][lenB] = lenA - i;

    14     for(int j = 0; j < lenB; j++) c[lenA][j] = lenB - j;

    15     c[lenA][lenB] = 0;

    16

    17     for(int i = lenA-1; i >= 0; i--)

    18         for(int j = lenB-1; j >= 0; j--)

    19         {

    20             if(strB[j] == strA[i])

    21                 c[i][j] = c[i+1][j+1];

    22             else

    23                 c[i][j] = minValue(c[i][j+1], c[i+1][j], c[i+1][j+1]) + 1;

    24         }

    25

    26     return c[0][0];

    27 }

     

     

    本文来自编程入门网:http://www.bianceng.cn/Programming/sjjg/200910/11747_3.htm

    最后再说说“备忘录”法。其实它算是动态规划的一种变形,它既具有通常的动态规划方法的效率,又采用了一种自顶向下的策略。其思想就是备忘原问题的自然但低效的递归算法。像在通常的动态规划中一样,维护一个记录了子问题解的表,但有关填表动作的控制结构更像递归算法。

     

    加了备忘的递归算法为每一个子问题的解在表中记录一个表项。开始时,每个表项最初都包含一个特殊的值,以表示该表项有待填入。当在递归算法的执行中第一次遇到一个子问题时,就计算它的解并填入表中。以后每次遇到该子问题时,只要查看并返回先前填入的值即可。

     

    下面是原文递归算法的做备忘录版本,并通过布尔变量memoize来控制是否使用备忘录,以及布尔变量debug来控制是否打印调用过程。有兴趣的读都可以通过这两个布尔变量的控制来对比一下备忘录版本与非备忘录版本的复杂度。

     

    备忘录版

     

      1 #include <iostream>

      2 #define M 100

      3

      4 using namespace std;

      5

      6 const bool debug = false; // Whether to print debug info

      7 const bool memoize = true; // Whether to use memoization

      8 unsigned int cnt = 0; // Line number for the debug info

      9

     10 int memoizedDistance[M][M]; // Matrix for memoiztion

     11

     12 int minValue(int a, int b, int c)

     13 {

     14     if(a < b && a < c) return a;

     15     else if(b < a && b < c) return b;

     16     else return c;

     17 }

     18

     19 /*

     20  * A recursive method which can be decorated by memoization.

     21  * Calculate from top to bottom.

     22  */

     23 int calculateStringDistance(string strA, int pABegin, int pAEnd, string strB, int pBBegin, int pBEnd)

     24 {

     25     if(memoize && memoizedDistance[pABegin][pBBegin] >= 0)

     26         return memoizedDistance[pABegin][pBBegin];

     27

     28     if(pABegin > pAEnd)

     29     {

     30         if(pBBegin > pBEnd)

     31         {

     32             if(memoize)

     33                 memoizedDistance[pABegin][pBBegin] = 0;

     34             if(debug)

     35                 cout << cnt++ << ": m(" << pABegin << "," << pBBegin << ")=0" << endl;

     36             return 0;

     37         }

     38         else

     39         {

     40             int temp = pBEnd - pBBegin + 1;

     41             if(memoize)

     42                 memoizedDistance[pABegin][pBBegin] = temp;

     43             if(debug)

     44                 cout << cnt++ << ": m(" << pABegin << "," << pBBegin << ")=" << temp << endl;

     45             return temp;

     46         }

     47     }

     48

     49     if(pBBegin > pBEnd)

     50     {

     51         if(pABegin > pAEnd)

     52         {

     53             if(memoize)

     54                 memoizedDistance[pABegin][pBBegin] = 0;

     55             if(debug)

     56                 cout << cnt++ << ": m(" << pABegin << "," << pBBegin << ")=0" << endl;

     57             return 0;

     58         }

     59         else

     60         {

     61             int temp = pAEnd - pABegin + 1;

     62             if(memoize)

     63                 memoizedDistance[pABegin][pBBegin] = temp;

     64             if(debug)

     65                 cout << cnt++ << ": m(" << pABegin << "," << pBBegin << ")=" << temp << endl;

     66             return temp;

     67         }

     68     }

     69

     70     if(strA[pABegin] == strB[pBBegin])

     71     {

     72         int temp = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);

     73         if(memoize)

     74             memoizedDistance[pABegin][pBBegin] = temp;

     75          if(debug)

     76             cout << cnt++ << ": m(" << pABegin << "," << pBBegin << ")=" << temp << endl;

     77         return temp;

     78     }

     79     else

     80     {

     81         int t1 = calculateStringDistance(strA, pABegin, pAEnd, strB, pBBegin+1, pBEnd);

     82         int t2 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin, pBEnd);

     83         int t3 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);

     84         int temp = minValue(t1, t2, t3) + 1;

     85         if(memoize)

     86             memoizedDistance[pABegin][pBBegin] = temp;

     87         if(debug)

     88             cout << cnt++ << ": m(" << pABegin << "," << pBBegin << ")=" << temp << endl;

     89         return temp;

     90     }

     91 }

     92

     93 int main()

     94 {

     95     if(memoize)

     96     {

     97         // initialize the matrix : memoizedDistance[][]

     98         for(int i = 0; i < M; i++)

     99             for(int j = 0; j < M; j++)

    100                 memoizedDistance[i][j] = -1; // -1 means unfilled cell yet

    101     }

    102

    103     string strA = "abcdfef";

    104     string strB = "a";

    105

    106     cout << endl << "Similarity = "

    107             << 1.0 / (1 + calculateStringDistance(strA, 0, (int)strA.length()-1, strB, 0, (int)strB.length()-1))

    108             << endl;

    109

    110     return 0;

    111 }

     

    总结 : 可以计算出,如果不用动态规划或是做备忘录,最坏情况下复杂度约为:lenA!*lenB!。使用动态规划的复杂度为O((lenA+1)*(lenB+1))。递归并做备忘录的方法最坏情况下复杂度为O((lenA+1)*(lenB+1))。

     

    在实际应用中,如果所有的子问题都至少要被计算一次,则一个自底向上的动态规划算法通常要比一个自顶向下的做备忘录算法好出一个常数因子,因为前者无需递归的代价,而且维护表格的开销也小些。此外,在有些问题中,还可以用动态规划算法中的表存取模式来进一步减少时间或空间上的需求。或者,如果子问题空间中的某些子问题根本没有必要求解,做备忘录方法有着只解那些肯定要求解的子问题的优点,对于本问题就是这样。

     

     

    本文来自编程入门网:http://www.bianceng.cn/Programming/sjjg/200910/11747_4.htm

     

    本文来自编程入门网:http://www.bianceng.cn/Programming/sjjg/200910/11747.htm

    题目:

    对于一个字符串a可以通过增加一个字符、删除一个字符、修改一个字符,将字符串a变成字符串b,例如

    a= abcddefg

    b = abcefg

    可以通过a字符串删除两个dd得到b字符串,也可以通过b字符串增加dd编程a字符串,从上面的分析可以知道,增加和删除的代价必须是相同的,这样a字符串变成b字符串的代价和b字符串变成a字符串的代价才会是相同的,否这可能产生代价不对称的情况。其实我们可以设定修改和增加(删除)的代价是不同的,当然也可以认为他们是一样的。

    实际的计算过程可以如下进行:

    1)比较a[i]和b[j];

    2)如果a[i] == b[j],那么distance = EditDistance(a[i + i], b[j + 1]) + 0;

    3)如果a[i] != b[j],那么可以经过如下操作使得a[i]等于b[j]

       a) a[i]前增加b[j],那么distance = EditDistance(a[i], b[j + 1] + insert_cost

       b)b[j]前增加a[i],那么distance = EditDistance(a[i + 1], b[j]) + insert_cost

       c)删除a[i],那么distance = EditDistance(a[i + 1], b[j]) + delete_cost

       d)删除b[j],那么distance = EditDistance(a[i], b[j + 1] + delete_cost

       e)a[i]变成b[j],那么distance = EditDistance(a[i + 1], b[j + 1] + replace_cost

       f)b[j]变成a[i],那么distance = EditDistance(a[i + 1], b[j + 1] + replace_cost

    如果insert_cost == delete_cost,那么a添加字符变成b和b删除字符变成a是等价的,a[i]变成b[j]与b[j]变成a[i]也是等价的,因此实际需要考虑的代价就是下面3种情况:

    i) distance = EditDistance(a[i], b[j + 1] + insert_cost(或delete_cost)

    ii) distance = EditDistance(a[i + 1], b[j] + insert_cost(或delete_cost)

    iii)distance = EditDistance(a[i + 1], b[j + 1] + replace_cost

    如果我们回顾一下最长公共子序列问题(LCS),就会发现这个问题和LCS问题几乎是等价的。因为可以这样理解,找出a和b的LCS,保持LCS对齐不变,增加删除一些字符就完成了变换,而这样的代价应该是最小的(猜测的,没有证明)

    这样一个问题,我们可以使用递归来解决。

    当然的解决方案是用动态规划的方法解决,采用动态规划解决时,我们假设前面字符串都已经变换相同了,那么在a[i]变成b[j]的过程中需要对比如下代价:

    1)如果a[i] == b[j],那么前面的状态可能是a[i - 1] b[j - 1]或者 a[i - 1] b[j]或者a[i] b[j - 1],我们要比较这些可能的转换过程中哪个代价更小;

    2)如果a[i] != b[j],那么:

       i)a[i] b[j]可能从a[i - 1] b[j - 1]状态通过replace a[i] to a[j] + replace的代价来实现;

      ii)a[i] b[j]可能从a[i ] b[j - 1]状态通过为a[i]前面添加一个b[j -1] + insert的代价来实现;

      iii)a[i] b[j]可能从a[i  - 1] b[j]状态通过删除a[i - 1] + delete的代价来实现;

    而这些代价就通过一个向量cost向量来存储。

    程序代码如下:

     

    [cpp] view plaincopy

    1. #include <stdio.h>  
    2. #include <string>  
    3.   
    4. int Min(int a, int b, int c) {  
    5.   int tmp = a > b ? b : a;  
    6.   tmp = tmp > c ? c : tmp;  
    7.   return tmp;  
    8. }  
    9. int EditDistance(const std::string& a, int a_offset, const std::string& b, int b_offset) {  
    10.   if (a_offset == a.size() && b_offset < b.size()) {  
    11.     return EditDistance(a, a_offset, b, b_offset + 1) + 1 ;  
    12.   } else if (b_offset == b.size() && a_offset < a.size()) {  
    13.     return EditDistance(a, a_offset + 1, b, b_offset) + 1;  
    14.   } else if (a_offset == a.size() && b_offset == b.size()) {  
    15.     return 0;  
    16.   } else {  
    17.     if (a[a_offset] == b[b_offset]) {  
    18.       return EditDistance(a, a_offset + 1, b, b_offset + 1);  
    19.     } else {  
    20.       int distance1 = EditDistance(a, a_offset + 1, b, b_offset) + 1;  
    21.       int distance2 = EditDistance(a, a_offset, b, b_offset + 1) + 1;  
    22.       int distance3 = EditDistance(a, a_offset + 1, b, b_offset + 1) + 1;  
    23.       return Min(distance1, distance2, distance3);  
    24.     }  
    25.   }  
    26. }  
    27. int EditDistance_DP(const std::string& a, const std::string& b) {  
    28.   int** cost = new int*[a.size() + 1];  
    29.   for (int i = 0; i < a.size() + 1; ++i) {  
    30.     cost[i] = new int[b.size() + 1];  
    31.   }  
    32.   for (int i = 0; i < a.size() + 1; ++i) {  
    33.     for (int j = 0; j < b.size() + 1; ++j) {  
    34.       cost[i][j] = 0;  
    35.     }  
    36.   }  
    37.   for (int i = 0; i < a.size(); ++i) {  
    38.     for (int j = 0; j < b.size(); ++j) {  
    39.       if (a[i] == b[j]) {  
    40.         cost[i + 1][j + 1] = Min(cost[i][j], cost[i][j + 1], cost[i + 1][j]);  
    41.       } else {  
    42.         cost[i + 1][j + 1] = Min(cost[i][j] + 1, cost[i][j + 1] + 1, cost[i + 1][j] + 1);  
    43.       }  
    44.     }  
    45.   }  
    46.   for (int i = 0; i <= a.size(); ++i) {  
    47.     for (int j = 0; j <= b.size(); ++j) {  
    48.       printf("%d  ", cost[i][j]);  
    49.     }  
    50.     printf(" ");  
    51.   }  
    52.   int distance = cost[a.size()][b.size()];  
    53.   for (int i = 0; i < a.size() + 1; ++i) {  
    54.     delete[] cost[i];      
    55.   }  
    56.   delete[] cost;  
    57.   return distance;  
    58. }  
    59. int main(int argc, char** argv) {  
    60.   std::string a = "adefk";  
    61.   std::string b = "bdefg";  
    62.   printf("edit distance = %d ", EditDistance(a, 0, b, 0));  
    63.   printf("edit distance = %d ", EditDistance_DP(a, b));  
    64. }  
    65.   
  • 相关阅读:
    第二节:依赖倒置原则和单一职责原则
    第一节:开闭原则和里氏替换原则
    leetcode 110 Balanced Binary Tree
    leetcode 102 Binary Tree Level Order Traversal
    leetcode 101 Symmetric Tree
    【产品】张小龙--微信背后的产品观
    【ML】目标检测及跟踪
    【设计】信息卡片设计
    【指标】游戏指标定义
    【GIT】windows本机搭建GIT服务器
  • 原文地址:https://www.cnblogs.com/fickleness/p/3154978.html
Copyright © 2020-2023  润新知