LCS:给出两个序列S1和S2,求出的这两个序列的最大公共部分S3就是就是S1和S2的最长公共子序列了。公共部分
必须是以相同的顺序出现,但是不必要是连续的。
解法一:在没学动态规划之前,我能想到的方法就是枚举了。将S1的所有子序列全部检查是否是S2的子序列,从中
选出最长公共子序列。对于长度为n的序列,其子序列共有2的n次方个,这样的话这种算法的时间复杂度就为指数级
了,这显然不太适合用于序列很长的求解了。
解法二:既然学到了动态规划,就来看看能否用动态规划的思想来解决这个问题。要使用动态规划,必须满足两个条
件:有最优子结构和重叠子问题。为了便于学习,我们先来了解下这两个概念。
如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子结构。当一个递归算法不断地调用同一问题
时,我们说该最优问题包含重叠子问题。
说明:动态规划要求其子问题既要独立又要重叠,这初看上去貌似存在矛盾,其实不然。只是这里说的是两个不同的
概念。独立指的是同一问题的两个子问题不共享资源。而重叠子问题指的是子问题可以作为不同的问题的子问题出
现。
设X = <x1,x2...xm>, Y = <y1,y2...yn>为两个序列,并设Z = <z1,z2...zk>为X和Y的任意一个LCS。可以得出:
1、如果xm = yn,那么zk = xm = yn而且Z(k-1)是X(m-1)和Y(n-1)的一个LCS;
2、如果xm != yn,那么zk != xm蕴含Z是X(m-1)和Y的一个LCS;
3、如果xm != yn,那么zk != yn蕴含Z是X和Y(n-1)的一个LCS。
注:上面的Z(k-1)表示序列Z<z1,z2...zn>,其中n=k-1。其它的X()和Y()也是一样的。
很容易证明上述三点是成立的,详细证明见算法导论。所以LCS具有最优子结构。从上面也可以看出LCS问题中的重
叠子问题的性质。所以我们可以用动态规划来解决LCS问题。由LCS问题的最优子结构可得出递归式:
下面来看看实现代码:
#include<iostream> #include<cstring> using namespace std; int c[100][100]; // c[i][j]表示序列S1前i个元素和S2的前j个元素的LCS int b[100][100]; //便于求解最优解 void LCS_Length (char x[], char y[]); void Print_LCS (char x[], int i, int j); int main() { char X[100], Y[100]; while (cin >> X >> Y) { LCS_Length (X, Y); Print_LCS (X, strlen(X), strlen(Y)); cout << endl; } return 0; } void LCS_Length (char x[], char y[]) { int m = strlen (x); int n = strlen (y); for (int i = 0; i <= m; i++) c[i][0] = 0; for (int i = 0; i <= n; i++) c[0][i] = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (x[i] == y[j]) { c[i + 1][j + 1] = c[i][j] + 1; b[i + 1][j + 1] = 0; } else if (c[i][j + 1] >= c[i + 1][j]) { c[i + 1][j + 1] = c[i][j + 1]; b[i + 1][j + 1] = 1; } else { c[i + 1][j + 1] = c[i + 1][j]; b[i + 1][j + 1] = 2; } } } } void Print_LCS (char x[], int i, int j) { if ((i == 0) || (j == 0)) return ; if (b[i][j] == 0) { Print_LCS (x, i - 1, j - 1); cout << x[i - 1] << ' '; } else if (b[i][j] == 1) Print_LCS (x, i -1, j); else Print_LCS (x, i, j - 1); }
书上后面给出了一种可以节省空间的方法,就是不要b数组,直接判断。因为c[i][j]仅依赖于c[i-1][i-1],c[i-1][j],c[i][j-1]。
这样的话只要小小修改下Print_LCS函数即可。