最长公共子序列问题(LCS)
给定两个序列X=?x1,x2,x3...xm?和Y=?y1,y2,y3...xn?,求X和Y的最长公共子序列。
例如:X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?,的最长公共子序列为?B,C,B,A?,长度为4;
对于此问题,可以采用暴力求解的方式来比对,即穷举出X的所有子序列,用每个子序列与y做一 一比较。假如X序列共有m个元素,对每个元素可以决定选或不选,则X的子序列个数共有2m个,可见与长度m呈指数阶,这种方法效率会很低。
动态规划
前几篇博文已介绍了采用动态规划的方法解决装配线问题及钢铁切割问题,它们都满足了两个条件:1.具有最优子结构的特征 2.子问题重叠。
最长公共子序列的特征
首先定义前缀Xi的含义为序列X=?x1,x2,x3...xn?的前i项组成的子序列,即Xi=?x1,x2,x3...xi?(0≤i≤n),X0为空串。
则有以下定理:
假如序列X=?x1,x2,x3...xm?,Y=?y1,y2,y3...xn?的一个LCS为Z=?z1,z2,z3...zk?
1.如果xm=yn,则zk=xm=yn,且Zk?1是Xm?1和Yn?1的一个LCS
2.如果xm≠yn,那么zk≠xm,则Z是Xm?1和Y的一个LCS
2.如果xm≠yn,那么zk≠yn,则Z是X和Yn?1的一个LCS
上述定理即为要求得X和Y的LCS,可以根据xm与yn的关系,如果xm=yn,则问题成了求Xm?1与Yn?1的LCS;如果xm≠yn,则问题变成求两对子序列的LCS, Xm?1,Yn的LCS及Xm,Yn?1的LCS,而且具有重叠子问题的性质都需要求Xm?1,Yn?1的LCS。
由以上分析:令c[i][j]为表示序列Xi和Yj的LCS的长度,则有:
c[i][j]=0,若i=0或者j=0
c[i][j]=c[i?1][j?1]+1,若i,j>0,且xi=yj
c[i][j]=max(c[i][j?1],c[i?1][j]),若i,j>0,且xi≠yj
计算LCS的长度
用数组c[m][n]保存子序列Xm,Yn的LCS的长度,由上分析,c[i][j]的值取决于c[i-1][j-1]、c[i][j-1]、c[i-1][j],可以采用自底向上的方法向上逐次计算,这样每个子问题只需要计算一次;用b[i][j]保存计算c[i][j]时,所选的子问题;
X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?
从上到下,从做到右:复杂度为Θ(m?n)
构造LCS
1.可以采用b[i][j]中记录的值,依次倒序输出,从b[7][6]开始倒序输出。也可以用递归的方法,从底层开始,正序输出;
2.不用b[i][j],即改进版,由于c[i][j]是由c[i-1][j-1]、c[i-1][j]、c[i][j-1],可以根据它们的关系,判断出方向;运行时间都为O(m+n);
例程
求X=?A,B,C,B,D,A,B?,和Y=?B,D,C,A,B,A?的一个LCS
/************************************************************************
CSDN 勿在浮沙筑高台
http://blog.csdn.net/luoshixian099
算法导论--动态规划(最长公共子序列)
2015年6月5日
main.cpp
************************************************************************/
#include<iostream>
using namespace std;
int b[8][7]={0},c[8][7]={0};
void Lcs_Length(char *X,char *Y) // 遍历序列X和Y,自底向上计算出c[i][j]
{
for(int i=1;i<=7;i++)
for(int j=1;j<=6;j++)
{
if(X[i] == Y[j])
{
c[i][j] = c[i-1][j-1]+1;
b[i][j] = 2; //左上方
}
else if(c[i-1][j] >= c[i][j-1])
{
c[i][j] = c[i-1][j];
b[i][j]=1; //上方
}
else
{
c[i][j] = c[i][j-1];
b[i][j] = 3; //左方
}
}
}
void Print_Lcs(int b[][7],char *X,int i,int j) //采用b[i][j]递归输出LCS
{
if(i == 0 || j == 0)
return ;
if(b[i][j] == 2)
{
Print_Lcs(b,X,i-1,j-1);
cout<<" "<<X[i];
}
else if(b[i][j] == 1)
Print_Lcs(b,X,i-1,j);
else
Print_Lcs(b,X,i,j-1);
}
void Print_Lcs(char *X,char *Y,int i,int j)//不用b[i][j],递归输出LCS
{
if(i == 0 || j == 0)
return ;
if(X[i] == Y[j])
{
Print_Lcs(X,Y,i-1,j-1);
cout<<" "<<X[i];
}
else if(c[i-1][j] >= c[i][j-1])
Print_Lcs(X,Y,i-1,j);
else
Print_Lcs(X,Y,i,j-1);
}
int main()
{
char X[]={‘ ‘,‘A‘,‘B‘,‘C‘,‘B‘,‘D‘,‘A‘,‘B‘};
char Y[]={‘ ‘,‘B‘,‘D‘,‘C‘,‘A‘,‘B‘,‘A‘};
Lcs_Length(X,Y);
for(int i=0;i<8;i++) //输出数组内的值
{
for(int j=0;j<7;j++)
cout<<" "<<c[i][j];
cout<<endl;
}
cout<<endl;
Print_Lcs(b,X,7,6); //输出LCS
cout<<endl;
Print_Lcs(X,Y,7,6); //改进的版本
cout<<endl;
return 0;
}