最长的公共子序列。
例如:X=(A,B,C,B,A,D,B),Y=(B,C,B,A,A,C),那么最长公共子
序列是 B,C,B,A;
分析:由于如果直接暴力破解,时间复杂度是我们避之不及的爆炸性指数。可以用动态规划分析其最优子结构,然后建立最优值的递归式,有点类似于我们中学所学数列中的通项,但还是有点区别。
(一)假设:已知Zk={z1,z2,z3.......zk}是X={x1,x2,x3,…,xm}和 Y={y1,y2,y3,…,yn}的最优子结构
①:当Xm=Yn=Zk时,Z={z1,z2.....zk-1}时,Zk-1是Xm-1和 Yn-1的最长公共子序列。
反证法:如果Zk-1不是Xm-1和 Yn-1的最长公共子序列。则存在一个序列M为Xm-1和 Yn-1的最长公共子序列,则M>Zk-1,那么在三序列后面加上Xm=Yn=Zk,因为M+Zk>Zk-1+Zk,则M+Zk为Xm和 Yn的最长公共子序列,这与假设冲突,所以Zk-1就是Xm-1和 Yn-1的最长公共子序列。
②当Xm≠Yn,Xm≠Zk时,我们可以先把Xm去掉,则Zk是Xm-1和Yn的最长公共子序列。
反证法:如果Zk不是Xm-1和Yn的最长公共子序列。则存在一个序列M是Xm-1和Yn的最长公共子序列,所以M>Z,当把Xm加到Xm-1处时,由于M>Z,则M是Xm和Yn的最长公共子序列。这与假设冲突。
③当Xm≠Yn,Yn≠Zk时,同②证法一样,Zk是Xm和Yn-1的最长公共子序列。
这说明这题具有最优子结构的性质,是典型的D题。
(二) 建立最优值递归式
用C[i][j]来存储最优值
①:当Xm=Yn=Zk时,C[i][j]=C[i1][j-1]+1;
②:当Xm≠Yn时,我们只需要比较Xm-1和Yn与Xm和Yn-1的最长公共子序列哪个更长;即:C[i][j]=max{C[i-1][j],C[i][j-1]};
当i=j=0时,C[i][j]=0;
(三)向上计算最优解
由于最优值只是得到了一个最长的一个数值,并不知道序列是什么,假设猜C[m][n]=5,我们知道这个最长公共子序列长度是5,那么这个5是怎样得到的?我们可以通过倒向追踪法知道5是从哪里来的。根据递归式:
当Xi=Yj时,C[i][j]=C[i-1][j-1]+1;
当Xi≠Yj时:C[i][j]=max{C[i-1][j],C[i][j-1]};
则C[i][j]有三个来源:C[i-1][j-1]+1和C[i-1][j]和C[i][j-1];
在建立最优值时可以用一个辅助数组W[i][j]来记录这三个来源:
C[i][j]=C[i-1][j-1]+1;W[i][j]=1;
C[i][j]=C[i][j-1];W[i][j]=2;
C[i][j]=C[i-1][j]; W[i][j]=3;
这样就可以根据W[m][n]的值来倒向追踪,当W[i][j]=1时;输出Xi,当W[i][j]=2时;追踪C[i][j-1];当W[i][j]=3时;追踪C[i-1][j];直到i=0或者j=0;
代码细节:
#include<iostream> #include<string.h> using namespace std; #define N 1002 int C[N][N], b[N][N]; char s1[N], s2[N]; int len1, len2; void LCSL() { for(int i=1;i<=len1;i++) for (int j = 1; j <=len2; j++) { if (s1[i-1] == s2[j-1]) { //如果当前字符串相同,则其公共子序列的长度为该字符串前的最长公共序列+1 C[i][j] = C[i - 1][j - 1] + 1; b[i][j] = 1;//标志每一步的来源 } else { if (C[i][j - 1] >= C[i - 1][j]) { C[i][j] = C[i][j - 1]; b[i][j] = 2; } else { C[i][j] = C[i - 1][j]; b[i][j] = 3; } } } } //倒向追踪每一步的结果 void print(int i, int j) { if (i == 0 || j == 0) return; if (b[i][j] == 1)//说明s1[i-1]==s2[j-1] { print(i - 1, j - 1); cout << s1[i - 1]; } else if (b[i][j] == 2)//由上面程序可知此时s1[i-1]≠s2[j-1]且最优解来源于C[i][j]=C[i][j-1] { print(i, j - 1);//所以递归回源点 } else print(i - 1, j); } int main() { cin >> s1; cin >> s2; //字符串的长度 len1 = strlen(s1); len2 = strlen(s2); for (int i= 0; i <= len1; i++) { C[i][0] = 0;//初始化第一列为 0 } for (int j = 0; j <= len2; j++) { C[0][j] = 0;//初始化第一行为 0 } LCSL(); //求解最长公共子序列 cout << "s1 和 s2 的最长公共子序列长度是:" << C[len1][len2] << endl; cout << "s1 和 s2 的最长公共子序列是:"; print(len1, len2); //递归构造最长公共子序列最优解 return 0; }