计算字符串的相似度

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

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

    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("\n");  
  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\n", EditDistance(a, 0, b, 0));  
  63.   printf("edit distance = %d\n", EditDistance_DP(a, b));  
  64. }  
  65.   

 

转载于:https://www.cnblogs.com/fickleness/p/3154978.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值