两个字符串的编辑距离-动态规划方法

两个字符串的编辑距离-动态规划方法[转载]

概念

字符串的编辑距离,又称为Levenshtein距离,由俄罗斯的数学家Vladimir Levenshtein在1965年提出。是指利用字符操作,把字符串A转换成字符串B所需要的最少操作数。其中,字符操作包括:

  • 删除一个字符     a) Delete a character
  • 插入一个字符     b) Insert a character
  • 修改一个字符     c) Replace a character

例如对于字符串"if"和"iff",可以通过插入一个‘f‘或者删除一个‘f‘来达到目的。

一般来说,两个字符串的编辑距离越小,则它们越相似。如果两个字符串相等,则它们的编辑距离(为了方便,本文后续出现的“距离”,如果没有特别说明,则默认为“编辑距离”)为0(不需要任何操作)。不难分析出,两个字符串的编辑距离肯定不超过它们的最大长度(可以通过先把短串的每一位都修改成长串对应位置的字符,然后插入长串中的剩下字符)。

 

问题描述

给定两个字符串A和B,求字符串A至少经过多少步字符操作变成字符串B。 

问题分析

1)首先考虑A串的第一个字符

假设存在两个字符串A和B,他们的长度分别是lenA和lenB。首先考虑第一个字符,由于他们是一样的,所以只需要计算A[2...lenA]和B[2...lenB]之间的距离即可。那么如果两个字符串的第一个字符不一样怎么办?可以考虑把第一个字符变成一样的(这里假设从A串变成B串):

  • 修改A串的第一个字符成B串的第一个字符,之后仅需要计算A[2...lenA]和B[2...lenB]的距离即可;
  • 删除A串的第一个字符,之后仅需要计算A[2...lenA]和B[1...lenB]的距离即可;
  • 把B串的第一个字符插入到A串的第一个字符之前,之后仅需要计算A[1...lenA]和B[2...lenB]的距离即可。

2)接下来考虑A串的第i个字符和B串的第j个字符。

我们这个时候不考虑A的前i-1字符和B串的第j-1个字符。如果A串的第i个字符和B串的第j个字符相等,即A[i]=B[j],则只需要计算A[i...lenA]和B[j...lenB]之间的距离即可。如果不想等,则:

  • 修改A串的第i个字符成B串的第j个字符,之后仅需要计算A[i+1...lenA]和B[j+1...lenB]的距离即可;
  • 删除A串的第i个字符,之后仅需要计算A[i+1...lenA]和B[j...lenB]的距离即可;
  • 把B串的第j个字符插入到A串的第i个字符之前,之后仅需要计算A[i...lenA]和B[j+1...lenB]的距离即可。

写到这里,自然会想到用递归求解或者动态规划求解,由于用递归会产生很多重复解,所以用动态规划。

建动态规划方程

用edit[i][j]表示A串和B串的编辑距离。edit[i][j]表示A串从第0个字符开始到第i个字符和B串从第0个字符开始到第j个字符,这两个字串的编辑距离。字符串的下标从1开始。

dis[0][0]表示word1和word2都为空的时候,此时他们的Edit Distance为0。很明显可以得出的,dis[0][j]就是word1为空,word2长度为j的情况,此时他们的Edit Distance为j,也就是从空,添加j个字符转换成word2的最小Edit Distance为j;同理dis[i][0]就是,word1长度为i,word2为空时,word1需要删除i个字符才能转换成空,所以转换成word2的最小Edit Distance为i。

则从上面的分析,不难推导出动态规划方程:

,其中

上式中的min()函数中的三个部分,对应三种字符操作方式:

edit[i-1][j]+1相当于给word2的最后插入了word1的最后的字符,插入操作使得edit+1,之后计算edit[i-1][j];

edit[i][j-1]+1相当于将word2的最后字符删除,删除操作edit+1,之后计算edit[i][j-1];

edit[i-1][j-1]+flag相当于通过将word2的最后一个字符替换为word1的最后一个字符。flag标记替换的有效次数。

算法分析:

也就是说,就是将一个字符串变成另外一个字符串所用的最少操作数,每次只能增加、删除或者替换一个字符。
  首先我们令word1和word2分别为:michaelab和michaelxy(为了理解简单,我们假设word1和word2字符长度是一样的),dis[i][j]作为word1和word2之间的Edit Distance,我们要做的就是求出michaelx到michaely的最小steps。

首先解释下dis[i][j]:它是指word1[i]和word2[j]的Edit Distance。dis[0][0]表示word1和word2都为空的时候,此时他们的Edit Distance为0。很明显可以得出的,dis[0][j]就是word1为空,word2长度为j的情况,此时他们的Edit Distance为j,也就是从空,添加j个字符转换成word2的最小Edit Distance为j;同理dis[i][0]就是,word1长度为i,word2为空时,word1需要删除i个字符才能转换成空,所以转换成word2的最小Edit Distance为i。下面及时初始化代码:

for (int i = 0; i < row; i++) dis[i][0] = i;
       for (int j = 0; j < col; j++) dis[0][j] = j;

    下面来分析下题目规定的三个操作:添加,删除,替换。
    假设word1[i]和word2[j](此处i = j)分别为:michaelab和michaelxy
    如果b==y, 
        那么:dis[i][j] = dis[i-1][j-1]。                                                              
    如果b!=y,
        那么:添加:也就是在michaelab后面添加一个y,那么word1就变成了michaelaby,
             此时  dis[i][j] = 1 + dis[i][j-1];
    上式中,1代表刚刚的添加操作,添加操作后,word1变成michaelaby,word2为michaelxy。
    dis[i][j-1]代表从word1[i]转换成word2[j-1]的最小Edit Distance,也就是michaelab转换成michaelx的最小
    Edit Distance,由于两个字符串尾部的y==y,所以只需要将michaelab变成michaelx就可以了,而他们之间的最
    小Edit Distance就是dis[i][j-1]。

    删除:也就是将michaelab后面的b删除,那么word1就变成了michaela,此时dis[i][j] = 1 + dis[i-1][j];
    上式中,1代表刚刚的删除操作,删除操作后,word1变成michaela,word2为michaelxy。dis[i-1][j]代表从
    word[i-1]转换成word[j]的最小Edit Distance,也就是michaela转换成michaelxy的最小Edit Distance,所以
    只需要将michaela变成michaelxy就可以了,而他们之间的最小Edit Distance就是dis[i-1][j]。

    替换:也就是将michaelab后面的b替换成y,那么word1就变成了michaelay,此时dis[i][j] = 1 + dis[i-1][j-1];
    上式中,1代表刚刚的替换操作,替换操作后,word1变成michaelay,word2为michaelxy。dis[i-1][j-1]代表从
    word[i-1]转换成word[j-1]的最小Edit Distance,也即是michaelay转换成michaelxy的最小Edit Distance,由
    于两个字符串尾部的y==y,所以只需要将michaela变成michaelx就可以了,而他们之间的最小Edit Distance就是
    dis[i-1][j-1]。


举例:

比如要计算cafe和coffee的编辑距离。cafe→caffe→coffe→coffee

先创建一个6×8的表(cafe长度为4,coffee长度为6,各加2)

(1):

      c o f f e e
                       
c                     
a                     
f                     
e                1

接着,在如下位置填入数字(表2):

      c o f f e e
   0 1 2 3 4 5 6
c 1                  
a 2                  
f 3                  
e 4             2

从3,3格开始,开始计算。取以下三个值的最小值:

  • 如果最上方的字符等于最左方的字符,则为左上方的数字。否则为左上方的数字+1。(对于3,3来说为0)
  • 左方数字+1(对于3,3格来说为2)
  • 上方数字+1(对于3,3格来说为2)

因此为格3,3为0(表3)

      c o f f e e
   0 1 2 3 4 5 6
c 1   0                
a 2                  
f 3                  
e 4             3     

循环操作,推出下表

      c o f f e e
   0 1 2 3 4 5 6
c 1 0 1 2 3    4    5   
a 2 1 1 2 3 4 5
f 3 2 2 1 2 3 4
e 4 3 3 2 2 2 3

取右下角,得编辑距离为3

代码:

[cpp] view plain copy

  1. <pre name="code" class="cpp">#include<stdio.h>
  2. #include<string.h>
  3. char s1[1000],s2[1000];
  4. int min(int a,int b,int c)
  5. {
  6. int tmp=a<b?a:b;
  7. return tmp<c?tmp:c;
  8. }
  9. void editDistance(int len1,int len2)
  10. {
  11. int **d=new int*[len1+1];
  12. for(int i=0;i<=len1;i++)
  13. d[i]=new int[len2+1];
  14. int i,j;
  15. for(i=0;i<=len1;i++)
  16. d[i][0]=i;
  17. for(j=0;j<=len2;j++)
  18. d[0][j]=j;
  19. for(i=1;i<=len1;i++)
  20. {
  21. for(j=1;j<=len2;j++)
  22. {
  23. int cost=s1[i]==s2[j]?0:1;
  24. int deletion=d[i-1][j]+1;
  25. int insertion=d[i][j-1]+1;
  26. int substitution=d[i-1][j-1]+cost;
  27. d[i][j]=min(deletion,insertion,substitution);
  28. }
  29. }
  30. printf("距离为:%d\n",d[len1][len2]);
  31. for(int i=0;i<=len1;i++)
  32. {
  33. delete[] d[i];
  34. }
  35. delete[] d;
  36. }
  37. int main()
  38. {
  39. while(scanf("%s%s",s1,s2)!=EOF)
  40. {
  41. editDistance(strlen(s1),strlen(s2));
  42. }
  43. }

概念

字符串的编辑距离,又称为Levenshtein距离,由俄罗斯的数学家Vladimir Levenshtein在1965年提出。是指利用字符操作,把字符串A转换成字符串B所需要的最少操作数。其中,字符操作包括:

  • 删除一个字符     a) Insert a character
  • 插入一个字符     b) Delete a character
  • 修改一个字符     c) Replace a character

例如对于字符串"if"和"iff",可以通过插入一个‘f‘或者删除一个‘f‘来达到目的。

一般来说,两个字符串的编辑距离越小,则它们越相似。如果两个字符串相等,则它们的编辑距离(为了方便,本文后续出现的“距离”,如果没有特别说明,则默认为“编辑距离”)为0(不需要任何操作)。不难分析出,两个字符串的编辑距离肯定不超过它们的最大长度(可以通过先把短串的每一位都修改成长串对应位置的字符,然后插入长串中的剩下字符)。

 

问题描述

给定两个字符串A和B,求字符串A至少经过多少步字符操作变成字符串B。 

问题分析

1)首先考虑A串的第一个字符

假设存在两个字符串A和B,他们的长度分别是lenA和lenB。首先考虑第一个字符,由于他们是一样的,所以只需要计算A[2...lenA]和B[2...lenB]之间的距离即可。那么如果两个字符串的第一个字符不一样怎么办?可以考虑把第一个字符变成一样的(这里假设从A串变成B串):

  • 修改A串的第一个字符成B串的第一个字符,之后仅需要计算A[2...lenA]和B[2...lenB]的距离即可;
  • 删除A串的第一个字符,之后仅需要计算A[2...lenA]和B[1...lenB]的距离即可;
  • 把B串的第一个字符插入到A串的第一个字符之前,之后仅需要计算A[1...lenA]和B[2...lenB]的距离即可。

2)接下来考虑A串的第i个字符和B串的第j个字符。

我们这个时候不考虑A的前i-1字符和B串的第j-1个字符。如果A串的第i个字符和B串的第j个字符相等,即A[i]=B[j],则只需要计算A[i...lenA]和B[j...lenB]之间的距离即可。如果不想等,则:

  • 修改A串的第i个字符成B串的第j个字符,之后仅需要计算A[i+1...lenA]和B[j+1...lenB]的距离即可;
  • 删除A串的第i个字符,之后仅需要计算A[i+1...lenA]和B[j...lenB]的距离即可;
  • 把B串的第j个字符插入到A串的第i个字符之前,之后仅需要计算A[i...lenA]和B[j+1...lenB]的距离即可。

写到这里,自然会想到用递归求解或者动态规划求解,由于用递归会产生很多重复解,所以用动态规划。

建动态规划方程

用edit[i][j]表示A串和B串的编辑距离。edit[i][j]表示A串从第0个字符开始到第i个字符和B串从第0个字符开始到第j个字符,这两个字串的编辑距离。字符串的下标从1开始。

dis[0][0]表示word1和word2都为空的时候,此时他们的Edit Distance为0。很明显可以得出的,dis[0][j]就是word1为空,word2长度为j的情况,此时他们的Edit Distance为j,也就是从空,添加j个字符转换成word2的最小Edit Distance为j;同理dis[i][0]就是,word1长度为i,word2为空时,word1需要删除i个字符才能转换成空,所以转换成word2的最小Edit Distance为i。

则从上面的分析,不难推导出动态规划方程:

,其中

上式中的min()函数中的三个部分,对应三种字符操作方式:

edit[i-1][j]+1相当于给word2的最后插入了word1的最后的字符,插入操作使得edit+1,之后计算edit[i-1][j];

edit[i][j-1]+1相当于将word2的最后字符删除,删除操作edit+1,之后计算edit[i][j-1];

edit[i-1][j-1]+flag相当于通过将word2的最后一个字符替换为word1的最后一个字符。flag标记替换的有效次数。

算法分析:

也就是说,就是将一个字符串变成另外一个字符串所用的最少操作数,每次只能增加、删除或者替换一个字符。
  首先我们令word1和word2分别为:michaelab和michaelxy(为了理解简单,我们假设word1和word2字符长度是一样的),dis[i][j]作为word1和word2之间的Edit Distance,我们要做的就是求出michaelx到michaely的最小steps。

首先解释下dis[i][j]:它是指word1[i]和word2[j]的Edit Distance。dis[0][0]表示word1和word2都为空的时候,此时他们的Edit Distance为0。很明显可以得出的,dis[0][j]就是word1为空,word2长度为j的情况,此时他们的Edit Distance为j,也就是从空,添加j个字符转换成word2的最小Edit Distance为j;同理dis[i][0]就是,word1长度为i,word2为空时,word1需要删除i个字符才能转换成空,所以转换成word2的最小Edit Distance为i。下面及时初始化代码:

for (int i = 0; i < row; i++) dis[i][0] = i;
       for (int j = 0; j < col; j++) dis[0][j] = j;

    下面来分析下题目规定的三个操作:添加,删除,替换。
    假设word1[i]和word2[j](此处i = j)分别为:michaelab和michaelxy
    如果b==y, 
        那么:dis[i][j] = dis[i-1][j-1]。                                                              
    如果b!=y,
        那么:添加:也就是在michaelab后面添加一个y,那么word1就变成了michaelaby,
             此时  dis[i][j] = 1 + dis[i][j-1];
    上式中,1代表刚刚的添加操作,添加操作后,word1变成michaelaby,word2为michaelxy。
    dis[i][j-1]代表从word1[i]转换成word2[j-1]的最小Edit Distance,也就是michaelab转换成michaelx的最小
    Edit Distance,由于两个字符串尾部的y==y,所以只需要将michaelab变成michaelx就可以了,而他们之间的最
    小Edit Distance就是dis[i][j-1]。

    删除:也就是将michaelab后面的b删除,那么word1就变成了michaela,此时dis[i][j] = 1 + dis[i-1][j];
    上式中,1代表刚刚的删除操作,删除操作后,word1变成michaela,word2为michaelxy。dis[i-1][j]代表从
    word[i-1]转换成word[j]的最小Edit Distance,也就是michaela转换成michaelxy的最小Edit Distance,所以
    只需要将michaela变成michaelxy就可以了,而他们之间的最小Edit Distance就是dis[i-1][j]。

    替换:也就是将michaelab后面的b替换成y,那么word1就变成了michaelay,此时dis[i][j] = 1 + dis[i-1][j-1];
    上式中,1代表刚刚的替换操作,替换操作后,word1变成michaelay,word2为michaelxy。dis[i-1][j-1]代表从
    word[i-1]转换成word[j-1]的最小Edit Distance,也即是michaelay转换成michaelxy的最小Edit Distance,由
    于两个字符串尾部的y==y,所以只需要将michaela变成michaelx就可以了,而他们之间的最小Edit Distance就是
    dis[i-1][j-1]。


举例:

比如要计算cafe和coffee的编辑距离。cafe→caffe→coffe→coffee

先创建一个6×8的表(cafe长度为4,coffee长度为6,各加2)

(1):

      c o f f e e
                       
c                     
a                     
f                     
e                1

接着,在如下位置填入数字(表2):

      c o f f e e
   0 1 2 3 4 5 6
c 1                  
a 2                  
f 3                  
e 4             2

从3,3格开始,开始计算。取以下三个值的最小值:

  • 如果最上方的字符等于最左方的字符,则为左上方的数字。否则为左上方的数字+1。(对于3,3来说为0)
  • 左方数字+1(对于3,3格来说为2)
  • 上方数字+1(对于3,3格来说为2)

因此为格3,3为0(表3)

      c o f f e e
   0 1 2 3 4 5 6
c 1   0                
a 2                  
f 3                  
e 4             3     

循环操作,推出下表

      c o f f e e
   0 1 2 3 4 5 6
c 1 0 1 2 3    4    5   
a 2 1 1 2 3 4 5
f 3 2 2 1 2 3 4
e 4 3 3 2 2 2 3

取右下角,得编辑距离为3

代码:

[cpp] view plain copy

  1. #include<stdio.h>
  2. #include<string.h>
  3. char s1[1000],s2[1000];
  4. int min(int a,int b,int c)
  5. {
  6. int tmp=a<b?a:b;
  7. return tmp<c?tmp:c;
  8. }
  9. void editDistance(int len1,int len2)
  10. {
  11. int **d=new int*[len1+1];
  12. for(int i=0;i<=len1;i++)
  13. d[i]=new int[len2+1];
  14. int i,j;
  15. for(i=0;i<=len1;i++)
  16. d[i][0]=i;
  17. for(j=0;j<=len2;j++)
  18. d[0][j]=j;
  19. for(i=1;i<=len1;i++)
  20. {
  21. for(j=1;j<=len2;j++)
  22. {
  23. int cost=s1[i]==s2[j]?0:1;
  24. int deletion=d[i-1][j]+1;
  25. int insertion=d[i][j-1]+1;
  26. int substitution=d[i-1][j-1]+cost;
  27. d[i][j]=min(deletion,insertion,substitution);
  28. }
  29. }
  30. printf("距离为:%d\n",d[len1][len2]);
  31. for(int i=0;i<=len1;i++)
  32. {
  33. delete[] d[i];
  34. }
  35. delete[] d;
  36. }
  37. int main()
  38. {
  39. while(scanf("%s%s",s1,s2)!=EOF)
  40. {
  41. editDistance(strlen(s1),strlen(s2));
  42. }
  43. }
时间: 2024-10-12 23:55:03

两个字符串的编辑距离-动态规划方法的相关文章

EditDistance,求两个字符串最小编辑距离,动态规划

问题描述: 题目描述Edit DistanceGiven two words word1 and word2, find the minimum number of steps required to convert word1 to word2. (each operation is counted as 1 step.)You have the following 3 operations permitted on a word:     a) Insert a character    

C#判断两个字符串是否相等的方法 ,还有char赋空值办法。

1 string str1="Test"; 2 string str2 = "Test"; 3 if (str1==str2) //第一种判断方式 4 { 5 //第二种判断方式 6 int result1 = str1.CompareTo(str2); 7 Console.WriteLine(result1); //输出result1=0 8 9 //第三种判断方式 10 int result2=String.Compare(str1, str2); 11 Con

TZOJ 1072: 编辑距离(动态规划)

1072: 编辑距离 时间限制(普通/Java):1000MS/10000MS     内存限制:65536KByte 总提交: 917            测试通过:275 描述 假设字符串的基本操作仅为:删除一个字符.插入一个字符和将一个字符修改成另一个字符这三种操作. 我们把进行了一次上述三种操作的任意一种操作称为进行了一步字符基本操作. 下面我们定义两个字符串的编辑距离:对于两个字符串a和b,通过上述的基本操作,我们可以把a变成b或b变成a,那么字符串a变成字符串b需要的最少基本字符操

C#比较两个字符串的相似度【转】

原文地址:http://www.2cto.com/kf/201202/121170.html 我们在做数据系统的时候,经常会用到模糊搜索,但是,数据库提供的模糊搜索并不具备按照相关度进行排序的功能. 现在提供一个比较两个字符串相似度的方法.通过计算出两个字符串的相似度,就可以通过Linq在内存中对数据进行排序和筛选,选出和目标字符串最相似的一个结果. 本次所用到的相似度计算公式是 相似度=Kq*q/(Kq*q+Kr*r+Ks*s) (Kq > 0 , Kr>=0,Ka>=0)其中,q是字

8.动态规划(1)——字符串的编辑距离

动态规划的算法题往往都是各大公司笔试题的常客.在不少算法类的微信公众号中,关于“动态规划”的文章屡见不鲜,都在试图用最浅显易懂的文字来描述讲解动态规划,甚至有的用漫画来解释,认真读每一篇公众号推送的文章实际上都能读得懂,都能对动态规划有一个大概了解. 什么是动态规划?通俗地理解来说,一个问题的解决办法一看就知道(穷举),但不能一个一个数啊,你得找到最优的解决办法,换句话说题目中就会出现类似“最多”.“最少”,“一共有多少种”等提法,这些题理论上都能使用动态规划的思想来求解.动态规划与分治方法类似

计算两个字符串的相似度---动态规划实现

问题描述:把两个字符串变成相同的基本操作定义如下:1.     修改一个字符(如把 a 变成 b)2.     增加一个字符 (如 abed 变成 abedd)3.     删除一个字符(如 jackbllog 变成 jackblog)针对于 jackbllog到jackblog 只需要删除一个或增加一个 l 就可以把两个字符串变为相同.把这种操作需要的次数定义为两个字符串的距离 L, 则相似度定义为1/(L+1) 即距离加一的倒数.那么jackbllog和jackblog的相似度为 1/1+1

string 构造方法为对象赋值,equals方法比较两个字符串是否相等

package String; /* * 写了一个简单的equals 方法 * 总结:用string的构造方法来赋值,构造方法:public string (string xxx) * 比较两个字符串是否相等,用方法 public boolean equals(string str) */ public class StringDemo { public static void main(String[] args) { String www = "hello"; //直接为www 初

两个字符串合并为一个字符串的各种方法

先定义两个字符串 $s1="qwe"; $s2="asd"; 方法一: 用.作为连接符是最简单的 $s1.$s2; 方法二: "{$s1}{$s2}"; 用""来将两个字符合成为一个字符,但是要注意两个字符外面均需要加上{}符号.比较好辨别,其实不加也可以. 方法三: 这种方法比较新颖,使用implode来连接字符注意''里面是没有空格的 implode('',array($s1,$s2)); 需要注意的是,不能使用+号来作为

jQuery使用serialize(),serializeArray()方法取得表单数据+字符串和对象类型两种表单提交的方法

原始form表单值获取方式(手动): $.ajax({ type: "POST", url: "ajax.php", data: "Name=摘取天上星&position=IT技术", success: function(msg){alert(msg);}, error: function(error){alert(error);} }); JQ serialize()方法取值: $.ajax({ type: "POST&quo