题目链接:http://poj.org/problem?id=3356
思路分析:题目为经典的编辑距离问题,其实质为动态规划问题;
编辑距离问题定义:给定一个字符串source,可以对其进行复制,替换,删除,增加操作,另外根据具体情况已经规定了每种操作的cost,现在要求求出一个操作序列,使其变为一个给定的字符串dest,并且该操作序列的cost的和最小(在该题目中复制开销为0,其他开销为1);
该问题为动态规划问题,先对该问题进行分析:
1)发掘最优子结构:
假设源字符串为S[0, 1, 2,..,n-1],其长度为n,目标字符串为D[0, 1, 2, ...., m-1],长度为m,则该问题转换为求一个cost最小的操作序列将
S[0,1,2,...,n-1]转换为D[0, 1, 2, ...., m-1];先考虑如何将S[0]转换为D[0];为了将S[0]转换为D[0],我们先选择一个操作,假设该操作是产生最
优的操作序列的第一个操作,这样我们就能将S[0]转换为D[0],则原来的问题在该操作后产生了一个子问题(在不同的动态规划问题中可能会产
生不同的子问题个数),即求一个cost最小的操作序列将S[1, 2, ... , n-1]转换为D[1, 2, ..., m-1],我们可以证明该子问题的最优解可以构造出原
来问题的最优解,这样我们就发现了该问题的最优子结构;另外,我们还需要考虑第一步的选择有多少种(不同动态规划问题的选择的可能性的
种类数目不同),即将S[0]转换为D[0]可以有多少种操作方法,明显在这个问题中有4种;
2)重叠子问题
如果该问题的递归算法反复地求解子问题,那么我们就称该最优化问题具有重叠子问题性质;在分治算法中,递归算法会生成全新的子问题,
子问题与子问题之间是无关的;而动态规划算法不同,如求斐波那契数列的递归算法中求数列f[n]需要求解f[n-1]与f[n-2],而f[n-1]=f[n-2]+f[n-3],
可以看到,在求斐波那契数列的递归算法中f[n-2]被求解多次,则求具有重叠子问题的结构,但是求斐波那契数列算法不是动态规划算法,因为其
不具有最优子结构,这里提出该问题是让大家对重叠子问题有一个具体的认识;现在,我们可以明显看到,在求解编辑距离问题中,我们做出一次
选择,就会产生一个子问题,因为在每一步中我们可能会做出多个选择,所以会求解多个相同的子问题,如在第一步中,我们可以选择复制(如果
S[0]==D[0]),删除,替换,增加操作,每个选择的操作就会产生相同的子问题,即求解S[1,2, .., m]转换D[1, 2, ..., n]的最小的cost的操作序列;
所以该问题具有重叠子问题;
3)解法:
对于该问题,我们刻画该问题的问题空间:假设dp[i][j]表示从字符串S[i, i+1, i+2, ...., m-1]转换为字符串D[j, j+1, j+2, ...., n-1]的操作序列的最小的cost和,
则原问题为求dp[0][0];
对于特殊的情况:
dp[i, n] = m-i,表示使S[i, i+1, ...,m-1]转换为空字符串,只能删除m-i个字符,所以最小的cost和为m-i;
dp[m, j] = n-j,表示使空字符串转换为D[j, j+1, ..., n-1],则只能增加n-j个字符,所以最小的cost和为n-j;
关于选择的可能:
<1>复制:dp[i, j] = dp[i+1, j+1], 如果S[i]==D[j],可以使用复制操作;
<2>删除:dp[i, j] = dp[i+1, j] + 1,删除操作cost为1,删除S[i],子问题为dp[i+1, j];
<3>替换:dp[i, j] = dp[i+1, j+1] + 1,替换操作只能在S[i]与D[j]不相等时才能选择;
<4>增加:dp[i, j] = dp[i, j+1] + 1,同样的,增加操作cost为1;
则该问题的动态规划方程已经给出,可以求解该问题;
代码如下:
#include <cstdio> #include <iostream> using namespace std; const int MAX_N = 1000 + 10; char source[MAX_N], dest[MAX_N]; int dp[MAX_N][MAX_N]; inline int Min(int a, int b) { return a > b ? b : a; } int main() { int s_len, d_len; while(scanf("%d %s", &s_len, source) != EOF) { scanf("%d %s", &d_len, dest); memset(dp, 0, sizeof(dp)); for (int i = 0; i <= d_len; ++i) dp[s_len][i] = d_len - i; for (int i = 0; i <= s_len; ++i) dp[i][d_len] = s_len - i; for (int i = s_len - 1; i >= 0; --i) { for (int j = d_len - 1; j >= 0; --j) { int min_cost = 100000; min_cost = Min(min_cost, dp[i + 1][j] + 1); min_cost = Min(min_cost, dp[i][j + 1] + 1); if (source[i] == dest[j]) min_cost = Min(min_cost, dp[i + 1][j + 1]); else min_cost = Min(min_cost, dp[i + 1][j + 1] + 1); dp[i][j] = min_cost; } } printf("%d\n", dp[0][0]); } return 0; }