子序列的定义:给定一个序列X=<x1,x2,...,xm>,另一个序列Z=<z1,z2,...,zk>,满足如下条件时称为X的子序列,
即存在一个严格递增的X的下标序列<i1,i2,...,ik>和所有的j=1,2,...,k,满足Zj=X[ik]下标序列,使得它们都相等。
如<B,C,D,B>是X=<A,B,C,B,D,A,B>的子序列。
给定两个序列X和Y,如果Z既是X的子序列,也是Y的子序列,我们称它是X和Y的公共子序列。
LCS问题:给定两个序列,求X和Y长度最长的公共子序列。
分析:LCS具有最优子结构性质,两个序列的LCS包含两个序列的前缀的LCS。
定义C[0,...,m][0,....,n],m是X序列的长度,Y序列的长度是n。C[i][j]的含义是序列X[0,...,i-1]和序列Y[0,...,j-1]的LCS。
如果X[i-1]==Y[j-1],则c[i][j]=c[i-1][j-1]+1;如果X[i-1]!=Y[j-1],则序列X[0,...,i-1]和序列Y[0,...,j-1]的LCS有两种可能,
一种是序列X[0,...,i-2]和序列Y[0,...,j-1]的LCS,另一种是序列X[0,...,i-1]和序列Y[0,...,j-2]的LCS,两者取较大值。
即c[i][j]=max(c[i][j-1],c[i-1][j])。
下面是实现的过程:
// LCS.cpp : 定义控制台应用程序的入口点。
//最长公共子序列问题:给定两个序列X=
和Y=
,求X和Y的最长公共子序列。
#include "stdafx.h"
#include
#include
#include
#include
using namespace std; //c[i][j]表示X的前i个字符和Y的前j个字符所共有的序列长度;b[i][j]用来回溯得到的公共子序列 void LCS_LENGTH(string X,string Y,vector
> & b,vector
> & c) { int m=X.length(),n=Y.length(); for(int i=0;i<=m;i++) c[i][0]=0; for(int j=0;j<=n;j++) c[0][j]=0; int i,j; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(X[i-1]==Y[j-1]) { 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]=0; } } //利用回溯和表b,追踪LCS void PRINT_LCS(vector
> & b, string X,int xlen, int ylen,string &s) { int i=xlen,j=ylen; if(i==0||j==0) return ; if(b[i][j]==2)//此时第i个元素X[i-1][j-1]属于公共元素 { PRINT_LCS(b,X,i-1,j-1,s); s+=X[i-1]; }else if(b[i][j]==1)//上面有c[i][j]=c[i-1][j];此时回溯c[i-1][j] PRINT_LCS(b,X,i-1,j,s); else PRINT_LCS(b,X,i,j-1,s); } int _tmain(int argc, _TCHAR* argv[]) { string X="ABCBDAB",Y="BDCABA"; int m=X.length(),n=Y.length(); vector
> b(m+1), c(m+1); string s; for(int i=0;i<=m;i++) { b[i].resize(n+1); c[i].resize(n+1); } LCS_LENGTH(X,Y,b,c); PRINT_LCS(b,X,m,n,s); cout<<"X:"<
<
它的时间复杂度是: O(m*n) 空间复杂度:O(m*n)
进一步优化:
动态规划解决LCS,一般要定义一个表c[0,...,m][0,...,n],所用的空间复杂的是O(m*n),其实这里表c的信息是冗余的。要想获得c[i][j]其实,只需根据c[i-1][j-1],c[i][j-1],c[i-1][j]中的信息获得即可。我们可以用O(m)或者O(n)的空间
就可以获得最长公共子序列的长度。我们这里定义一个数组a[m+1],存储如图中绿色部分的信息,a[0]=c[i-1][j-1],
当进行到比较X[i]与Y[j]时,我们要得到c[i,j]的值,它需要用到c[i-1][j-1],c[i][j-1],c[i-1][j],即a[0],a[i],a[i-1]。同时,它是新的a[i],如第二张图的红色部分所示。同时,原来a[0]里存储的数据用过后,再下一次循环就用不上了,a[0]也需要更新。然后,这次循环结束后,就形成了一个新的a数组。我们可以看到,它是从上到下,一列列的生成c[i][j]中的元素的。注意,当生成了一列结束,即j>=m时,开始新的外一层循环,此时,设置a[0]=0.
下面的代码在原有的基础增加了一个函数LCS(....),用O(m+1)的空间获得LCS长度,这里没有恢复重构LCS中的元素。
// LCS.cpp : 定义控制台应用程序的入口点。
//最长公共子序列问题:给定两个序列X=
和Y=
,求X和Y的最长公共子序列。
#include "stdafx.h"
#include
#include
#include
#include
using namespace std; //这里对算法进行了优化,空间复杂度O(m+1) void LCS(string X, string Y, vector
& a) { int m=X.length(),n=Y.length(); for (int j = 1; j <= n; j++) { a[0]=0;//内层循环结束,a[0]要置零,想象求a[1][j]所依赖的a[0][j-1]=0,即是初始化步骤 for (int i = 1; i <= m; i++) { int pre=a[0]; int temp = a[i]; if (X[i - 1] == Y[j - 1]) a[i] =pre+1; else a[i] = max(a[i],a[i-1]); a[0]=temp; } } } //c[i][j]表示X的前i个字符和Y的前j个字符所共有的序列长度;b[i][j]用来回溯得到的公共子序列 void LCS_LENGTH(string X,string Y,vector
> & b,vector
> & c) { int m=X.length(),n=Y.length(); for(int i=0;i<=m;i++) c[i][0]=0; for(int j=0;j<=n;j++) c[0][j]=0; int i,j; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(X[i-1]==Y[j-1]) { 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]=0; } } //利用回溯和表b,追踪LCS void PRINT_LCS(vector
> & b, string X,int xlen, int ylen,string &s) { int i=xlen,j=ylen; if(i==0||j==0) return ; if(b[i][j]==2)//此时第i个元素X[i-1][j-1]属于公共元素 { PRINT_LCS(b,X,i-1,j-1,s); s+=X[i-1]; }else if(b[i][j]==1)//上面有c[i][j]=c[i-1][j];此时回溯c[i-1][j] PRINT_LCS(b,X,i-1,j,s); else PRINT_LCS(b,X,i,j-1,s); } int _tmain(int argc, _TCHAR* argv[]) { string X="ABCBDABEF",Y="BDCABAEF"; int m=X.length(),n=Y.length(); vector
> b(m+1), c(m+1); string s; for(int i=0;i<=m;i++) { b[i].resize(n+1); c[i].resize(n+1); } LCS_LENGTH(X,Y,b,c); PRINT_LCS(b,X,m,n,s); cout<<"X:"<
<
a(m+1); LCS(X,Y ,a); cout<
<