一、问题描述
设A和B是2个字符串。要用最少的字符操作将字符串A转换为字符串B。这里所说的字符操作包括:(1)删除一个字符;(2)插人一个字符;(3)将一个字符改为另一个字符。将字符串A变换为字符串B所用的最少字符操作数称为字符串A到B的编辑距离,记为d(AB)试设计一个有效算法,对任给的2个字符串A和B,计算出它们的编辑距离d(A.B)。
算法设计:对于给定的字符串A和字符串B,计算其编辑距离d(AB)。
数据输入:由文件input.txt提供输入数据。文件的第1行是字符串A,文件的第2行是字符串 B
结果输出:将编辑距离d(A,B)输出到文件output.txt的第1行。
输入文件示例 输出文件示例
input.txt output.txt
fxpimu 5
xwrs
二、题目分析与设计思路
本道题目存在最优子结构性质并且子问题之间存在联系适合使用递归和动态规划来做,递归算法存在大量重复计算,更适合用动态规划来做。解决完所有子问题就能得到最终解,子问题是将字符串a的子串转化为字符串b的子串所需要最小的编辑距离(这里子串是指以父串首字符为头,父串第i个字符为尾的连续字串)。
比较两个子串的末尾字符,根据比较情况进行讨论:
①两者相等,无需对末尾字符进行编辑,编辑距离就应该等于以第i-1和j-1字符结尾的子串的编辑距离,所以d[i][j]=d[i-1][j-1]。
②两者不相等时,此时需要进行编辑操作(只编辑a子串的末尾字符),有三种操作可供选择:
1)修改,a,b两个子串长度相等时才能使用修改操作,修改只是对最后一个字符进行改变,并不影响前面的所有字符,编辑距离就应该等于a的以第i-1和b的第j-1字母结尾的子串的编辑距离加1,所以d[i][j]=d[i-1][j-1]+1。
2)删除或插入, 如果a子串比b子串短就选择插入(添加一个跟b子串相同的末尾字符),如果a子串比b子串长就选择删除a的末尾字符。插入时,编辑距离就应该等于以a的第i-1和b的以j字母结尾的子串的编辑距离加1,所以d[i][j]=d[i-1][j]+1。删除时,编辑距离就应该等于以a的第i和以b的第j-1字母结尾的子串的编辑距离加1,所以d[i][j]=d[i][j-1]+1。
这三种操作中选择能得到最小编辑数的操作,d[i][j]=min(d[i-1][j],d[i][j-1],d[i-1][j-1])+1,其实不必关心具体进行哪种编辑操作,得到当前的最小编辑数即可。
a\b | ||
d[i-1][j-1] | d[i-1][j] | |
d[i][j-1] | d[i][j] (当前) |
三、代码与运行结果
#include <iostream>
#include<string.h>
#include<fstream>
#include <cmath>
using namespace std;
int d[20][20];//默认为0
int function(char *s1, char *s2) {
int l1 = strlen(s1);
int l2 = strlen(s2);
if (l1 == 0) return l2;
if (l2 == 0) return l1;
for (int i = 0; i <= l1; i++) {
d[i][0] = i;
}
for (int j = 0; j <=l2; j++) {
d[0][j] = j;
}
for (int i = 1; i <= l1; i++) {
for (int j = 1; j <=l2; j++) {
if (s1[i - 1] == s2[j - 1]) d[i][j] = d[i - 1][j - 1];
else d[i][j] = min(d[i - 1][j - 1], min(d[i - 1][j], d[i][j - 1])) + 1;
}
}
return d[l1][l2];
}
int main() {
ifstream in("C://Users//86133//Desktop/input.txt");
ofstream out("C://Users//86133//Desktop/output.txt");
char s1[20];
char s2[20];
in >> s1;
in >> s2;
int a = function(s1, s2);
out << a;
in.close();
out.close();
return 0;
}
input.txt:
output.txt:
四、时间复杂度分析
定义一个二维数组d[i][j]用来储存s1[0-i]修改为s2[0-j]需要的编辑数,首先判断一下特殊情况,即有一个字符串为空,然后进行初始化第一行第一列。遍历数组d,比较末尾字符s1[i-1]和s2[j-1],根据比较情况的不同进行讨论。
当s1[i-1]==s2[j-1]时,对末位字符不需要进行编辑,故d[i][j]=d[i-1][j-1];当s1[i-1]!=s2[j-1]时,需要对其中之一的末位进行编辑,此时d[i][j]=min(d[i-1][j],d[i][j-1],d[i-1][j-1])+1;最后打印到output.txt文件中。
时间复杂度分析:
设a字符串长度为n,b字符串长度为m,则d数组空间为n*m,d数组中的数值都要被计算一次,因此需要计算n*m次,故时间复杂度为:O(n*m)=O(n^2)。
五、总结与体会
总结
通常可按照一下4个步骤设计,
1.找出最优解的性质,并刻画其结构特征
2.递归地定义最优值
3.以自底向上的方式计算最优值
4.根据计算最有值时得到的信息,构造最优解。
体会:
我认为做动态规划算法的题目需要做这三点很关键的两步:
1.确定一维dp数组或者二维dp数组所代表的实际意义。
2.找出子问题的关系,列dp[i][j]与dp[i-1][j-1],dp[i][j-1],dp[i-1][j]
的关系表达式
3.如何初始化dp数组,大部分是初始化数组的边缘。