最长公共子序列问题LCS
问题描写叙述:
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X= { x1, x2,…, xm},则还有一序列Z= {z1, z2,…, zk}是X的子序列是指存在一个严格递增的下标序列 {i1, i2,…, ik},使得对于全部j=1,2,…,k有 Xij=Zj。
比如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列。对应的递增下标序列为{2,3,5,7}。给定两个序列X和Y,当还有一序列Z既是X的子序列又是Y的子序列时。称Z是序列X和Y的公共子序列。
比如。若X= { A, B, C, B, D, A, B}和Y= {B, D, C, A, B, A},则序列{B,C,A}是X和Y的一个公共子序列,序列{B,C,B,A}也是X和Y的一个公共子序列。并且,后者是X和Y的一个最长公共子序列,由于X和Y没有长度大于4的公共子序列。给定两个序列X= {x1, x2, …, xm}和Y= {y1, y2, … , yn}。要求找出X和Y的一个最长公共子序列。
问题解析:
设X= { A, B, C, B, D, A, B},Y= {B, D, C, A, B, A}。
求X。Y的最长公共子序列最easy想到的方法是穷举法。
对X的多有子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列。由集合的性质知,元素为m的集合共同拥有2^m个不同子序列,因此。穷举法须要指数级别的运算时间。进一步分解问题特性。最长公共子序列问题实际上具有最优子结构性质。
设序列X={x1,x2,……xm}和Y={y1,y2,……yn}的最长公共子序列为Z={z1,z2,……zk}。
则有:
(1)若xm=yn,则zk=xm=yn,且zk-1是Xm-1和Yn-1的最长公共子序列。
(2)若xm!=yn且zk!=xm,则Z是Xm-1和Y的最长公共子序列。
(3)若xm!=yn且zk!=yn,则Z是X和Yn-1的最长公共子序列。
当中,Xm-1={x1,x2……xm-1}。Yn-1={y1,y2……yn-1},Zk-1={z1,z2……zk-1}。
-
若xm=yn(最后一个字符同样),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时。问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。
-
若xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。因为zk≠xm与zk≠yn当中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y),类似的,若zk≠yn 则有Z∈LCS(X , Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。
递推关系:
用c[i][j]记录序列Xi和Yj的最长公共子序列的长度。当中,Xi={x1,x2……xi}。Yj={y1,y2……yj}。
当i=0或j=0时。空序列是xi和yj的最长公共子序列。此时,c[i][j]=0;当i,j>0,xi=yj时,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]}。由此建立递推关系例如以下:
构造最优解:
由以上分析可知。要找出X={x1,x2,……xm}和Y={y1,y2,……yn}的最长公共子序列,能够按一下方式递归进行:当xm=yn时,找出xm-1和yn-1的最长公共子序列,然后在尾部加上xm(=yn)就可以得X和Y的最长公共子序列。当Xm!=Yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者为X和Y的最长公共子序列。设数组record[i][j]记录c[i][j]的值由哪一个子问题的解得到的,从record[m][n]開始。依其值在数组b中搜索,当record[i][j]=1时。表示Xi和Yj的最长公共子序列是由Xi-1和Yj-1的最长公共子序列在尾部加上xi所得到的子序列。当record[i][j]=2时,表示Xi和Yj的最长公共子序列与Xi-1和Yj-1的最长公共子序列同样。当record[i][j]=3时,表示Xi和Yj的最长公共子序列与Xi和Yj-1的最长公共子序列同样。
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
#define MAX 1000
using namespace std;
int dp[MAX][MAX];
int record[MAX][MAX];
int LCSLength(string strA,string strB)
{
int i,j;
int lenA=strA.length();
int lenB=strB.length();
for(i=0;i<lenA;i++)
dp[i][0]=0;
for(j=0;j<lenB;j++)
dp[0][j]=0;
for(i=0;i<lenA;i++)
{
for(j=0;j<lenB;j++)
{
if(i==0||j==0)
{
if(strA[i]==strB[j])
{
dp[i][j]=1;
record[i][j]=1;
}
else
{
if(i>0)
{
dp[i][j]=dp[i-1][j];
record[i][j]=3;
}
if(j>0)
{
dp[i][j]=dp[i][j-1];
record[i][j]=2;
}
}
}
else if(strA[i]==strB[j])
{
dp[i][j]=dp[i-1][j-1]+1;
record[i][j]=1;
}
else if(dp[i-1][j]>=dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
record[i][j]=3;
}
else
{
dp[i][j]=dp[i][j-1];
record[i][j]=2;
}
}
}
return dp[lenA-1][lenB-1];
}
void LCS(int i,int j,string strA)
{
if(i<0||j<0)
return ;
if(record[i][j]==1)
{
LCS(i-1,j-1,strA);
cout<<strA[i];
}
else if(record[i][j]==2)
LCS(i,j-1,strA);
else
LCS(i-1,j,strA);
}
int main(int argc,char *argv[])
{
string strA,strB;
cin>>strA>>strB;
cout<<"LCSLength: "<<LCSLength(strA,strB)<<endl;
cout<<"LCS: ";
LCS(strA.length()-1,strB.length()-1,strA);
cout<<endl;
cout<<"Record:\n";
for(int i=0;i<strA.length();i++)
{
for(int j=0;j<strB.length();j++)
printf("%3d",record[i][j]);
printf("\n");
}
return 0;
}
在这里处理边界条件(i=0||j=0)分类比較麻烦,事实上还能够把它们合并为一条语句;
<span style="font-size:18px;">#inc</span><span style="font-size: 18px;">lude<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#define MAX 1000
using namespace std;
int dp[MAX][MAX];
int record[MAX][MAX];
int LCSLength(string strA,string strB)
{
int i,j;
int lenA=strA.length();
int lenB=strB.length();
for(i=0;i<=lenA;i++)
dp[i][0]=0;
for(j=0;j<=lenB;j++)
dp[0][j]=0;
for(i=1;i<=lenA;i++)
{
for(j=1;j<=lenB;j++)
{
if(strA[i-1]==strB[j-1])//注意下标从0開始。所以这里有些许不一样
{ //dp[i][j]存储的是Xi-1与Yj-1的最长LCS
dp[i][j]=dp[i-1][j-1]+1;
record[i][j]=1;
}
else if(dp[i-1][j]>=dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
record[i][j]=3;
}
else
{
dp[i][j]=dp[i][j-1];
record[i][j]=2;
}
}
}
return dp[lenA][lenB];
}
void LCS(int i,int j,string strA)
{
if(i==0||j==0)
return ;
if(record[i][j]==1)
{
LCS(i-1,j-1,strA);
cout<<strA[i-1];
}
else if(record[i][j]==2)
LCS(i,j-1,strA);
else
LCS(i-1,j,strA);
}
int main(int argc,char *argv[])
{
string strA,strB;
cin>>strA>>strB;
cout<<"LCSLength: "<<LCSLength(strA,strB)<<endl;
cout<<"LCS: ";
LCS(strA.length(),strB.length(),strA);
cout<<endl;
cout<<"Record:\n";
for(int i=0;i<=strA.length();i++)
{
for(int j=0;j<=strB.length();j++)
printf("%3d",record[i][j]);
printf("\n");
}
return 0;
}
</span>
LCSLength函数在计算最优值时。分别迭代X。Y构造数组b,c。设数组每一个元素单元计算耗费时间O(1),则易得算法LCSLength的时间复杂度为O(mn)。在算法LCS中,根据数组b的值回溯构造最优解,每一次递归调用使i。或j减小1。
从而算法的计算时间为O(m+n)。
LCS的回溯构造最优解步骤例如以下图所看到的: