什么是子序列呢?子序列就是从给定的序列中随意的(不一定是连续的)去掉若干个元素(也可能一个也不去掉)后所形成的序列。如X=ABCBDAB,Y=BCDB就是X的一个子序列。
最长公共子序列问题描述为:给定序列X=x1x2x3x4...xm, Y=y1y2y3y4...yn. 求出序列X和Y的最长公共子序列Z。
我们首先想到了用穷举法,首先列举出序列X的所有子序列,并一一检测是不确Y的子序列,并设置一个变量来记录当前的最长公共子序列,这种方法比较直观,但是当序列为长序列的时候,这种方法耗费的时间将是非常可观的,需要指数级时间。而利用动态规划将可以有效的解决这个问题。
那应该如何利用动态规划法呢?从哪开始思考? 我们还记得,要看一个问题是否能够用动态规划法,首先要看此问题是否具有最优子结构特征,那好,我们用从这里开始入手。下面的定理证明了LCS的最优子结构(这个定理具体是怎么来的,如何证明的可看看相关书籍)。
定理:设给定序列X=x1x2x3x4...xm, Y=y1y2y3y4...yn. Z=z1z2z3...zk是X和Y的一个最长公共子序列。
1,如果xm==yn,那么zk==xm==yn,而Z=z1z2z3...zk-1是序列X=x1x2x3x4...xm-1, Y=y1y2y3y4...yn-1.的一个最长公共子序列,这里要注意:两个序列的公共子序列并不是唯一的,可能有多个
2,如果xm!=yn,这样就有三种情况,1),最长公共子序列包括xm,zk==xm,这个时候就要去除yn,因为既然包括了xm,而且这是序列的最后一个元素,所以,必定不包含yn,这样最长公共子序列就成了求序列X=x1x2x3x4...xm, Y=y1y2y3y4...yn-1.的一个最长公共子序列 .2) 最长公共子序列包括yn,zk==yn,这个时候就要去除xm,因为既然包括了yn,而且这是序列的最后一个元素,所以,必定不包含xm,这样最长公共子序列就成了求序列X=x1x2x3x4...xm-1, Y=y1y2y3y4...yn.的一个最长公共子序列. 3) 最长公共子序列两个都不包括zk!=xm!=yn,这种情况,去掉哪一个都一样(这种情况用处不大)。
那要怎么设计程序呢?题目要求是求最长公共子序列,那我们就要记录在一步步遍历子问题过程中所求的当前最长序列长度。
我们假设X,Y的最长公共子序列为LCS[m,n];根据上面的定理,我们写出状态转移方程:LCS[m,n]=max{LCS[m-1,n],LCS[m,n-1]}
这样看来,我们在进行求解的过程中就在决定两个序列中哪一个元素要放入最长公共子序列中。那么该怎么决定呢,就是根据上面的方程LCS[m,n]=max{LCS[m-1,n],LCS[m,n-1]}。。
我们知道,动态规划方法本质就是遍历所有可能的子问题,最终求解到子问题。这里的子问题是什么呢,根据上面分析,就是i(0=<i<=m)长度的序列和j(0<=j<=n)长度序列的最长子序列
所以我们应该定义一个二维数组来表示i长度的序列和j长度的序列的最长子序列长度。LCS[m][n]. X序列是行,Y序列是列。
需要注意的是当某一个序列的长度为0时,那么当前最优值为0。
下面是核心代码
for(int i=0;i<8;++i)
length[0][i]=0;
for(i=0;i<7;++i)
length[i][0]=0;
for(i=1;i<7;++i)
{
for(int j=1;j<8;++j)
{
if(X[i-1]==Y[j-1])
{
length[i][j]=length[i-1][j-1]+1;
flag[i][j]=0;//如果这两个相等,标志为0
}
else
{
if(length[i-1][j]>=length[i][j-1])
{
length[i][j]=length[i-1][j];
flag[i][j]=1;如果子序列为LCS[i-1][j],标志为1
}
else
{
length[i][j]=length[i][j-1];
flag[i][j]=2;//如果子序列为LCS[i][j-1],标志为2 }
}
}
我们每当计算某一个子问题时,都要把它想象成是最终的问题。这样才能更好的运用之前的状态转移方程,因为我们这个方程就是从后往前给推出来的
上边代码中的flag是为了在求出最优解之后,然后构造最优解保存的信息。那需要保存哪些信息才有助于我们构造呢。根我们前面分析,我们需要从后往前来推测某一个元素是不是最长公共子序列中的元素,只要看看if(xm==yn).如果相等的话,说明这是本次序列中的一个公共序列的最后一个元素,如果不等的话,那就往前递推,以相同的规则来构造前一个子问题的最优解。这很明显可以设计成一个递归结构。所以我们保存了如上所述信息
我们可以构造下面的递归函数:
void OutPut(int flag[7][8],int i,int j,char x[6])
{
if(i==0 || j==0)
return ;
if(flag[i][j]==0)
{
OutPut(flag,i-1,j-1,x);
cout<<x[i-1]<<" ";
}
else if(flag[i][j]==1)
OutPut(flag,i-1,j,x);
else
OutPut(flag,i,j-1,x);
}
问题解决了,发现这个问题有点像0-1背包问题。.。