编辑距离递归算法 - Skiena

时间:2013-10-07 05:50:41

标签: c++ c algorithm dynamic-programming

我正在阅读Steven Skiena撰写的算法设计手册,我正处于动态编程章节。他有一些编辑距离的示例代码,并使用了一些既不在书中也不在互联网上解释的功能。所以我想知道

a)该算法如何运作?

b)indel和match函数有什么作用?

#define MATCH     0       /* enumerated type symbol for match */
#define INSERT    1       /* enumerated type symbol for insert */
#define DELETE    2       /* enumerated type symbol for delete */

int string_compare(char *s, char *t, int i, int j)
{
        int k;                  /* counter */
        int opt[3];             /* cost of the three options */
        int lowest_cost;        /* lowest cost */

        if (i == 0) return(j * indel(' '));
        if (j == 0) return(i * indel(' '));

        opt[MATCH] = string_compare(s,t,i-1,j-1) + match(s[i],t[j]);
        opt[INSERT] = string_compare(s,t,i,j-1) + indel(t[j]);
        opt[DELETE] = string_compare(s,t,i-1,j) + indel(s[i]);

        lowest_cost = opt[MATCH];
        for (k=INSERT; k<=DELETE; k++)
                if (opt[k] < lowest_cost) lowest_cost = opt[k];

        return( lowest_cost );
}

6 个答案:

答案 0 :(得分:23)

在书中的第287页:

int match(char c, char d)
{
  if (c == d) return(0); 
  else return(1); 
}

int indel(char c)
{
  return(1);
}

答案 1 :(得分:5)

基本上,它利用动态编程方法解决问题,将问题的解决方案构建为子问题的解决方案,避免重新计算,无论是自下而上还是自上而下。

问题的递归结构如给定here,其中i,j分别是两个字符串中的起始(或结束)索引。

enter image description here

以下是this page的摘录,可以很好地解释算法。

  

问题:给定两个大小为m的字符串,n和一组操作替换   (R),插入(I)和删除(D)全部以相同的成本。找到最小数量   将一个字符串转换为另一个字符串所需的编辑(操作)。

     

识别递归方法:

     

在这种情况下会出现什么问题?考虑找到编辑距离   部分字符串,比如小前缀。我们将它们表示为   [1 ... i]和[1 ... j]为1&lt; 1。我&lt; m和1 < j&lt; ñ。显然它是   解决最终问题的较小实例,将其表示为E(i,j)。我们的   目标是找到E(m,n)并最小化成本。

     

在前缀中,我们可以用三种方式对齐字符串(i, - ),   ( - ,j)和(i,j)。连字符符号( - )表示无字符。一个   例子可以说得更清楚。

     

给出字符串SUNDAY和SATURDAY。我们想把SUNDAY转换成   星期六有最少的编辑。让我们选择i = 2和j = 4即前缀   字符串分别是SUN和SATU(假设字符串索引   从1)开始。最右边的字符可以对齐三个   不同的方式。

     

案例1:对齐字符U和U.它们相等,不需要编辑。   我们仍然留下了i = 1和j = 3,E(i-1,j-1)的问题。

     

案例2:将第一个字符串中的右字符对齐,而不是第一个字符串   第二串。我们需要删除(D)。我们还有问题   i = 1且j = 4,E(i-1,j)。

     

案例3:将第二个字符串中的右字符对齐,而不是第二个字符串   第一串。我们需要插入(I)。我们还是离开了   问题i = 2且j = 3,E(i,j-1)。

     

结合所有子问题,最小化对齐前缀字符串的成本   结束于由

给出的i和j      

E(i,j)= min([E(i-1,j)+ D],[E(i,j-1)+ I],[E(i-1,j-1)+ R如果   i,j字符不相同])

     

我们还没有完成。什么是基本情况?

     

当两个字符串的大小均为0时,成本为0.只有一个   字符串为零,我们需要编辑操作为非零   长度字符串。在数学上,

     

E(0,0)= 0,E(i,0)= i,E(0,j)= j

我建议您浏览this lecture以获得更好的解释。

如果两个字符不匹配(在最终答案中添加了一个移动),则函数match()返回1,否则返回0。

答案 2 :(得分:4)

他们在书中解释了。请阅读 8.2.4编辑距离的种类

部分

答案 3 :(得分:3)

请浏览此链接: https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/styles/pages/editdistance.html

实现上述算法的代码是:

int dpEdit(char *s1, char *s2 ,int len1,int len2)
{
if(len1==0)  /// Base Case
return len2;
else if(len2==0)
return len1;
else
{
    int add, remove,replace;
    int table[len1+1][len2+2];
    for(int i=0;i<=len2;i++)
    table[0][i]=i;
    for(int i=0;i<=len1;i++)
    table[i][0]=i;
    for(int i=1;i<=len1;i++)
    {
        for(int j=1;j<=len2;j++)
        {
          // Add 
          //
          add = table[i][j-1]+1;  
          remove = table[i-1][j]+1;
          if(s1[i-1]!=s2[j-1])
          replace = table[i-1][j-1]+1;
          else
          replace =table[i-1][j-1];
          table[i][j]= min(min(add,remove),replace); // Done :)

        }
    }

答案 4 :(得分:1)

这是一种递归算法而不是动态编程。请注意,i&amp; j指向s&amp;的最后一个字符分别在算法开始时。

indel返回1。 如果a = b(匹配)则匹配(a,b)返回0,否则返回1(替换)

#define MATCH     0       /* enumerated type symbol for match */
#define INSERT    1       /* enumerated type symbol for insert */
#define DELETE    2       /* enumerated type symbol for delete */

int string_compare(char *s, char *t, int i, int j)
{
    int k;                  /* counter */
    int opt[3];             /* cost of the three options */
    int lowest_cost;        /* lowest cost */

    // base case, if i is 0, then we reached start of s and 
    // now it's empty, so there would be j * 1 edit distance between s & t
    // think of it if s is initially empty and t is not, how many
    // edits we need to perform on s to be similar to t? answer is where
    // we are at t right now which is j
    if (i == 0) return(j * indel(' '));
    // same reasoning as above but for s instead of t
    if (j == 0) return(i * indel(' '));

    // calculate opt[match] by checking if s[i] = t[j] which = 0 if true or 1 if not
    // then recursively do the same for s[i-1] & t[j-1]
    opt[MATCH] = string_compare(s,t,i-1,j-1) + match(s[i],t[j]);
    // calculate opt[insert] which is how many chars we need to insert 
    // in s to make it looks like t, or look at it from the other way,
    // how many chars we need to delete from t to make it similar to s?
    // since we're deleting from t, we decrease j by 1 and leave i (pointer
    // in s) as is + indel(t[j]) which we deleted (always returns 1)
    opt[INSERT] = string_compare(s,t,i,j-1) + indel(t[j]);
    // same reasoning as before but deleting from s or inserting into t
    opt[DELETE] = string_compare(s,t,i-1,j) + indel(s[i]);

    // these lines are just to pick the min of opt[match], opt[insert], and
    // opt[delete]
    lowest_cost = opt[MATCH];
    for (k=INSERT; k<=DELETE; k++)
            if (opt[k] < lowest_cost) lowest_cost = opt[k];

    return( lowest_cost );
}

算法不难理解,你只需要阅读几次。总是让我开心的是发明它的人以及递归会做正确事情的信任。

答案 5 :(得分:0)

到目前为止,这对于OP来说可能不是问题,但我会写下对文本的理解。

/**
 * Returns the cost of a substitution(match) operation
 */
int match(char c, char d)
{
  if (c == d) return 0
  else return 1
}

/**
 * Returns the cost of an insert/delete operation(assumed to be a constant operation)
 */
int indel(char c)
{
  return 1
}

编辑距离本质上是将给定字符串转换为另一个参考字符串所需的最小修改次数。如您所知,可以进行以下修改。

  1. 替换(替换单个字符)
  2. 插入(将单个字符插入字符串)
  3. 删除(从字符串中删除单个字符)

现在

  

正确提出字符串相似性问题需要我们设置每个字符串转换操作的成本。为每个操作分配等价的1会定义两个字符串之间的编辑距离。

因此可以确定,我们已知的三个修改中的每一个都有恒定的成本O(1)。

但是我们怎么知道在哪里修改?

我们改为从字符串的末尾逐字符查找可能需要或不需要的修改。所以,

  1. 我们计算从字符串末尾开始的所有替换操作
  2. 我们计算从字符串末尾开始的所有删除操作
  3. 我们计算所有从字符串末尾开始的插入操作

最后,一旦有了这些数据,我们将返回上述三个总和的最小值。