最长公共子序列问题(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;
}