算法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 得到,则只需对 Yj 做 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)本题的思路 和 课本中的例题“最长公共子序列”相似,但不能拘泥于课本中的代码,只能参考思路,否则容易形成 通过修改课本中给出的代码来做题 的思路,对以后做题没有帮助

浙公网安备 33010602011771号