算法01

编辑距离问题 —— 动态规划

设A和B是2个字符串。要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括 (1)删除一个字符; (2)插入一个字符; (3)将一个字符改为另一个字符。 将字符串A变换为字符串B所用的最少字符操作数称为字符串A到 B的编辑距离,记为d(A,B)。 对于给定的字符串A和字符串B,计算其编辑距离 d(A,B)。

输入格式:

第一行是字符串A,文件的第二行是字符串B。

提示:字符串长度不超过2000个字符。

输出格式:

输出编辑距离d(A,B)

输入样例:

在这里给出一组输入。例如:

fxpimu,

输出样例:

在这里给出相应的输出。例如:

5
 
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
 
一开始拿到这道题时,虽然知道要用动态规划完成,但是没有具体的思路。后来老师讲解之后,get到了这道题的做法。
一、分析最优解性质
    两个字符串Xm和Yn之间的最优修改,一定是包含了它们的各自前缀之间的最优修改。
    子问题:Xm-1和Yn,Xm和Yn-1,Xm-1和Yn-1
    反证:如果存在前缀之间的更优的修改,则X和Y的修改只需在前缀的最优修改基础上做一次删除、增加或改写或什么都不做。这样,X和Y就得到了更优的修改方式,矛盾。
               所以,两个字符串之间的最优修改,一定是包含了它们的各自前缀之间的最优修改。
 
二、建立递归式
    令要求解的问题是Xi和Yj,将X转换为Y,则其求解可以在以下几种基础上进行(例如X = aba,Y = acsa)
   (1)Yj-1已由 Xi 得到,则只需对 Y做 add 操作(a[ i ][ j-1 ] + 1)
   (2)Yj 已由 Xi-1 得到,则只需再做 delete 操作(a[ i-1 ][ j ] + 1)
   (3)Yj-1已由Xi-1 得到,则此时若 X[ i ] == Y[ j ],则什么都不用做,否则改写操作( a[ i-1 ][ j-1 ] + 1 or a[ i-1 ][ j-1 ])
     Xi 和Yj 的最优解将是上面三种情况中使用步骤最少的。
 
      伪代码如下:
if i == 0 且 j == 0, edit(i, j) = 0

if i == 0 且 j > 0, edit(i, j) = j

if i > 0 且 j == 0, edit(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

 

     完整代码如下:
#include <iostream>
#include <cstring>
using namespace std ;

char str1[2000] ; 
char str2[2000] ;

int a[2000][2000] ; //辅助数组 a
    
/*int edit(int i, int j)
{
    //三个最简单情形 
    if(i == 0 && j == 0)
    {
        return 0 ; //此时str1,str2都为空,不用任何操作
    }
    if(i == 0 && j > 0)
    {
        return j ; //此时只需要增加 j 个字符即可
    }
    if(i > 0 && j == 0)
    {
        return i ; //此时只需要删减 i 个字符即可 
    }
    
    //当第一个字符串中的第 i 个字符不等于第二个字符串的第 j 个字符时,需要改写,t = 1 ;否则(不需要改写),t = 0;
    int t, max ;
    
    if(i >= 1 && j >= 1)
    {
        if(str1[i] != str2[j])
        {
            t = 1 ;
        }
        else t = 0 ;
        
        max = (edit(i-1, j)+1 < edit(i, j-1)+1) ? edit(i-1, j)+1 : edit(i, j-1)+1 ;
        max = (edit(i, j) < edit(i-1, j-1)+t) ? edit(i, j) : edit(i-1, j-1)+t ;
    }
    
    return max ;
}*/

int main()
{
    cin >> str1 ;
    cin >> str2 ;
    
    int len1 = strlen(str1) ;
    int len2 = strlen(str2) ;
    
//填表(注意i,j的下标!!!)
for(int i=1 ; i<=len1 ; i++) a[i][0] = i ; //i行0列:表示第一个字符串有 i 个字符,第二个字符串有 0 个字符,此时需要做 i 次add操作 for(int j=1 ; j<=len2 ; j++) a[0][j] = j ; //0行j列:表示第一个字符创有 0 个字符,第二个字符串有 j 个字符,此时需要做 j 次delete操作 for(int i=1 ; i<=len1 ; i++) { for(int j=1 ; j<=len2 ; j++) { if(str1[i-1] == str2[j-1]) a[i][j] = a[i-1][j-1] ; //如果两个字符串的当前字符相等,那么Xi,Yj的操作次数就和Xi-1,Yj-1相同 else a[i][j] = min( min(a[i-1][j-1], a[i-1][j]), a[i][j-1] ) + 1 ; //如果两个字符串的当前字符不等,那么Xi,Yj的操作次数就取最少的,然后再进行1次改写操作(当前字符不等) } } cout << a[len1][len2] << endl ; //直接输出a[len1][len2]即可——此时已经检查完两个字符串中的所有字符 return 0 ; }

 

心得:(1)在做动态规划题目的时候,要按照 分析最优解性质——构造递归式 的步骤去做

           (2)灵活运用作为辅助的数据结构,如本题中的 a 数组

           (3)本题的思路 和 课本中的例题“最长公共子序列”相似,但不能拘泥于课本中的代码,只能参考思路,否则容易形成 通过修改课本中给出的代码来做题 的思路,对以后做题没有帮助

posted @ 2020-10-26 17:36  咕咕鸽子王  阅读(64)  评论(0)    收藏  举报