一、最长公共子序列问题(注意:不是连续子序列):
子序列是指在原序列中删去若干元素(这些元素可以不相邻)后得到的序列。例如X=abcbdab,Y=bdcaba,bca和bcba都是X和Y的公共子序列,且后者是最长的公共子序列。给定两个序列X(i)={x[1],x[2],......x[i]}和Y(j)={y[1],y[2],......y[j]},如何找出它们的一个最长公共子序列?
动态规划算法求解过程如下:
1. 设c[i][j]表示X(i)和Y(j)的最长公共子序列长度。
(1)若x[i]=y[j],则X(i)和Y(j)的最长公共子序列c[i][j]=c[i-1][j-1]+1。
(2)若x[i]!=y[j],必须解决两个子问题,即找出X(i-1)和Y(j)的一个最长公共子序列及X(i)和Y(j-1)的一个最长公共子序列,这两者中的较长者即为原问题的解,因此c[i][j]=max{c[i][j-1],c[i-1][j]}。
(3)为打印出最长公共子序列,用b[i][j]标识c[i][j]是由上述三种子问题中的哪一种计算出来的,是由X(i-1)和Y(j-1)的LCS在尾部加上x[i]所得到,还是由X(i-1)和Y(j)的LCS得到,还是由X(i)和Y(j-1)的LCS得到。根据b[i][j]我们可以递归地打印出最长公共子序列的结果。
2. 具体计算c[i][j]如下:
void LCSLength(int m,int n,char* x,char* y,int** c,char** b){
int i,j;
for(i=1;i<=m;i++) c[i][0]=0;
for(i=1;i<=n;i++) c[0][i]=0;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
if(x[i]==y[j]){
c[i][j]=c[i-1][j-1]+1;
b[i][j]='A';
}else if(c[i-1][j]>=c[i][j-1]){
c[i][j]=c[i-1][j];
b[i][j]='B';
}else{
c[i][j]=c[i][j-1];
b[i][j]='C';
}
}
3. 根据计算最优值得到的信息构造最优解:b[i][j]保存了计算c[i][j]的方式,据此可构造出最优解。
void LCS(int i,int j,char* x,char** b){
if(i==0 || j==0) return;
if(b[i][j]=='A'){
LCS(i-1,j-1,x,b);
cout<<x[i];
}else if(b[i][j]=='B'){
LCS(i-1,j,x,b);
}else {
LCS(i,j-1,x,b);
}
}
二、如果将上述问题更改为:求最长公共子串(注意:是连续的),该如何解答呢?
仍然用动态规划来解决,但状态方程的定义需要改一下:
1. 如果序列X和序列Y存在最长公共子串,那么这个子串肯定是X、Y序列的一部分,必然是以某个x[i], y[j]结尾(且x[i] == y[j])。那么我们就可以这样来定义:假设X、Y存在以x[i],y[j] 结尾的公共子串,用c[i][j] 表示这个公共子串可能的最大长度。很明显有如下状态方程:
(1)若x[i]=y[j],则c[i][j]=c[i-1][j-1]+1
(2)若x[i]!=y[j],则c[i][j]=0
是不是很简单,跟上面的计算方式几乎一摸一样,只需要在x[i]==y[j]的时候,直接给c[i][j] = 0 就可以了。
void LCSLength(int m,int n,char* x,char* y,int** c){
int i,j;
for(i=1;i<=m;i++) c[i][0]=0;
for(i=1;i<=n;i++) c[0][i]=0;
for(i=1;i<=m;i++) {
for(j=1;j<=n;j++) {
if (x[i] == y[j]) {
c[i][j] = c[i-1][j-1]+1;
} else {
c[i][j] = 0;
}
}
}
}