动态规划:找出所有的LCS
用动态规划法求解问题特性:a.具有重叠性;b.具有最优子结构性质
1.最长公共子序列(LCS)的概念:
若Z<X,Z<Y,且不存在比Z更长的X和Y 的公共子序列,
则称Z是X和Y 的最长公共子序列,记为ZÎLCS(X , Y)。
最长公共子序列往往不止一个。
e.g. X=<A,B,C,B,D,A,B>, Y=<B,D,C,A,B,A>, 则
Z=<B,C,B,A>, Z’=<B,C,A,B>, Z’’=<B,D,A,B>
均属于LCS(X , Y) ,即X,Y有3个LCS。
2.寻找最长公共子序列
由书上分析结果:
(1)若xm=yn,则问题化归成求Xm-1与Yn-1的LCS
(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)
(2)若xm≠yn 则问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS
LCS(X , Y)的长度为:
max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}
3.求取最长公共子序列
引进一个二维数组C,用C[i,j]记录Xi与Yj的LCS的长度
如果我们是自底向上进行递推计算,那么在计算C[i,j]之前,
C[i-1,j-1], C[i-1,j]与C[i,j-1]均已计算出来。此时我们
根据X[i]=Y[j]还是X[i]¹Y[j],就可以计算出C[i,j]:
若X[i]=Y[j],则执行C[i,j]←C[i-1,j-1]+1;若X[i]¹Y[j],则根据:
C[i-1,j]≥C[i,j-1],则C[i,j]取C[i-1,j];否则C[i,j]取C[i,j-1]。即有
C[i,j]=
e.g. 如下图:
0 1 2 3 4 5 6
yj B D C A B A
0 xi 0 0 0 0 0 0 0
←↑ ↑ ←↑ ↖ ↖
1 A 0 ← 0 ← 0 ← 0 1 ← 1 1
↖ ↑ ↖
2 B 0 1 ← 1 ← 1 ← 1 2 ← 2
↑ ←↑ ↖ ←↑ ←↑
3 C 0 1 ← 1 2 ←2 ← 2 ← 2
↖ ←↑ ↑ ←↑ ↖
4 B 0 1 ← 1 2 ← 2 3 ← 3
↑ ↖ ←↑ ←↑ ↑ ←↑
5 D 0 1 2 ← 2 ← 2 3 ← 3
↑ ↑ ←↑ ↖ ←↑ ↖
6 A 0 1 2 ← 2 3 ← 3 4
↖ ↑ ←↑ ↑ ↖ ←↑
7 B 0 1 2 ← 2 3 4 ← 4
为了搜索到所有的LCS的,使用一个m´n的二维数组b,
b[i,j]记录C[i,j]是通过哪一个子问题的值求得的,
以决定搜索的方向:
若C[i-1,j]>C[i,j-1],则b[i,j]中记入“↑”;
若C[i-1,j]=C[i,j-1]时,则b[i,j]中记入“←↑”,
若C[i-1,j]< C[i,j-1],则b[i,j]中记入“←”;
执行的搜索方向:
X[i]=Y[j]要执行b[i,j]=“↖”,
X[i]¹Y[j]且C[i,j-1] > C[i-1,j]要执行b[i,j]=“←”,
X[i]¹Y[j]且C[i,j-1] < C[i-1,j] 要执行b[i,j]=“↑”,
X[i]¹Y[j]且C[i,j-1] = C[i-1,j] 要执行b[i,j]=“←↑”,
算法 LCS_L(X,Y,m,n,C)
for i=0 to m do C[i,0]←0
for j=1 to n do C[0,j]←0
for i=1 to m do {
for j=1 to n do{
if x[i]=y[j] then {C[i,j]←C[i-1,j-1]+1;
b[i,j]←“↖” ;}
}else if C[i-1,j]>C[i,j-1]
then {
C[i,j]←C[i-1,j];
b[i,j]←“↑” ;
}
else if C[i-1,j]=C[i,j-1]
then {
C[i,j]←C[i-1,j];
b[i,j]←“←↑” ;
}
}else{C[i,j]←C[i,j-1];
b[i,j]←“←” ;}
算法的时间复杂度显然是Q(m×n)的。
4 程序如下:
// 输出LCS.cpp
#include<windows.h>
#include<time.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<iostream>
using namespace std;
//*******************************寻找最长公共子序列***********************//
void LCS_L(int X[],int Y[],int m,int n,int C[][10],char D[][10])
{
int i,j,k,l;
for(i=0;i<=m;i++) //给数组C、D边界赋值
{
C[i][0]=0;
D[i][0]='0';
} //i=0时取值为0
for(j=0;j<=n;j++)
{
C[0][j]=0;
D[0][j]='0';
} //j=0时取值为0
for(i=1;i<=m;i++) //获取C[i][j]的值
{
for(j=1;j<=n;j++)
{
if(X[i-1]==Y[j-1]) //两数组的元素相同值相等
{
C[i][j]=C[i-1][j-1]+1;//
D[i][j]='$';
}
else
{
if(C[i-1][j]>C[i][j-1])//比较大小求取C、D两数组的对应值
{
C[i][j]=C[i-1][j];
D[i][j]='>';
}
else if(C[i-1][j]==C[i][j-1])
{
C[i][j]=C[i-1][j];
D[i][j]='=';
}
else
{
C[i][j]=C[i][j-1];
D[i][j]='<';
}
}
}
}
cout<<endl<<"输出二维数组b"<<endl;
for(k=0;k<=10;k++) //输出二维数组b
{
for(l=0;l<=9;l++)
cout<<D[k][l]<<" ";
cout<<endl;
}
//调用显示最长公共子序列函数
}
//***********************输出一个最长公共子序列**********************//
void LCS_OutputOne(int C[][10],int X[],int Y[],int i,int j)
{
if(i==0||j==0)
{
cout<<endl<<"An LCSLength string: ";
return;
} //到边界是返回
if(X[i-1]==Y[j-1]) //b[i][j]=='$'
{
LCS_OutputOne(C,X,Y,i-1,j-1); //对角线查找
cout<<X[i-1];
}
else
if(C[i-1][j]>C[i][j-1])
LCS_OutputOne(C,X,Y,i-1,j); // b[i][j]=='>'
else if(C[i-1][j]<C[i][j-1])
LCS_OutputOne(C,X,Y,i,j-1); //b[i][j]='<'
else
LCS_OutputOne(C,X,Y,i,j-1); //b[i][j]='='
}
//***********************输出所有最长公共子序列**********************//
void LCS_OutputAll(int C[][10],char D[][10],int X[],int i,int j,int Result[],int len,int k)
{
if(i==0 || j==0)//到边界时返回
{
int n;
for(n=0;n<k;n++)
{
cout<<Result[n]; //输出数值
}
cout<<endl;
return;
}
else
{if(C[i][j]==C[i-1][j-1]+1)//左上走
{
Result[len-1]=X[i-1];//取数组值
len--;
LCS_OutputAll(C,D,X,i-1,j-1,Result,len,k);
len++;
}
else
{
if(D[i][j]=='>')//向上走
{
LCS_OutputAll(C,D,X,i,j-1,Result,len,k);
}
else if(D[i][j]=='<')//向左走
{
LCS_OutputAll(C,D,X,i-1,j,Result,len,k);
}
else
{
LCS_OutputAll(C,D,X,i,j-1,Result,len,k);
LCS_OutputAll(C,D,X,i-1,j,Result,len,k);
}
}
}
}
//***************************主函数********************************//
int main()
{
int i,j,k,x,y;
int len=0;
int A[10]={0};
int B[9]={0};
int C[11][10]={0};
char D[11][10]={0};
system("color FC");
srand(time(0));
cout<<"数组A:";
for(i=0;i<10;i++)//
{
A[i]=rand()%3;
cout<<A[i]<<" ";
}
cout<<endl<<"数组B:";
for(j=0;j<9;j++)//
{
B[j]=rand()%3;
cout<<B[j]<<" ";
}
LCS_L(A,B,10,9,C,D);//
cout<<"显示二维数组C"<<endl;
for(i=0;i<11;i++) //显示二维数组C
{
for(j=0;j<10;j++)
cout<<C[i][j]<<" ";
cout<<endl;
}
len=C[i-1][j-1];//
x=10;
y=9;
k=len;
int Result[len];
cout<<endl<<"lcs长度为:"<<len;
LCS_OutputOne(C,A,B,x,y);
cout<<endl<<endl;
cout<<"所有的Lcs如下:"<<endl;
LCS_OutputAll(C,D,A,x,y,Result,len,k);
cout<<endl<<"结束";
return 0;
}
5.结果分析:
由LCS_Output_All函数可知,求出所有的LCS的长度就是根据数组b[i][j]中的值,即搜索的方向信息来遍历所有可能的路径找到满足条件的元素集合。函数LCS_L的时间复杂度是求解数组b和c的执行时间,是O(mn+m+n)。而函数LCS_Output_All的时间复杂度取决于遍历的路径数。在最好的情况下,即X序列和Y序列完全相等,m=n,数组b中的值都是‘$’(指向对角线方向),所以时间复杂度是O(m)。而在最坏情况下,即X序列和Y序列不存在公共子序列,数组b中的值都是“4”,就是要分别沿向上和向左的方向搜索,这是每处理一次X[i]和Y[j],都必须沿着两个方向调用函数LCS_Output_All,当遇到i=0或j=0的情况开始返回,只有在搜索完所有的路径后算法才结束。要确定最坏情况的时间复杂度,就是要计算出从点(m,n)到i=0或j=0(除(i,j)=(0,0)外)的所有路径。