最长公共子序列问题

最长公共子序列问题

  问题描述:给定两个序列X,YX,Y(可以把它们视作字符串),求他们的最长公共子序列。例如X="BCAB",Y="BDACB"X="BCAB",Y="BDACB",其最长公共子序列为BCB,BABBCB,BAB。一个给定序列的子序列,就是将给定序列零个或多个元素去掉之后的序列。例如X="BAC"X="BAC",它有B,A,C,BA,BC,AC,BACB,A,C,BA,BC,AC,BAC,六个子序列。如何利用动态规划求解这个问题?(最长公共子序列问题,Longest common subsequence,LCS)

  求解动态规划问题,首先都要找到它是否满足最优子结构特征(最优子结构特征是指最优化问题的最优解可以由子问题的最优解组合得到)。

  假设X=x1x1x2xn,Y=y1y1y2ymX=x_1x_1x_2……x_n,Y=y_1y_1y_2……y_m,假设其最长公共子序列为Z=z1z2zkZ = z_1z_2……z_k

  考虑xn=ymx_n=y_m时的情况,假设xn=ymzkx_n=y_m\ne z_k,那么LCS(X,Y)=LCS(Xn1,Ym1)\text{LCS}(X,Y)=\text{LCS}(X_{n-1},Y_{m-1}),则存在LCS(Xn1,Ym1)+xn\text{LCS}(X_{n-1},Y_{m-1})+x_n的更长的子序列,因此,xn=ymx_n=y_m时,xn=ym=zkx_n=y_m=z_k

  当xnymx_n\ne y_m时,可以得到Z=max(LCS(Xn1,Ym),LCS(Xn,Ym1))Z=\max(\text{LCS}(X_{n-1},Y_m),\text{LCS}(X_{n},Y_{m-1}))(取两者较长的那个)。

  综上得到:
Z={LCS(Xn1,Ym1)+xnxn=ymmax(LCS(Xn1,Ym),LCS(Xn,Ym1))xnym Z=\begin{cases}\text{LCS}(X_{n-1},Y_{m-1})+x_n&x_n=y_m\\\max(\text{LCS}(X_{n-1},Y_m),\text{LCS}(X_{n},Y_{m-1}))& x_n\ne y_m\end{cases}
上述可以说明这个问题满足最优子结构特征。但是如果通过存储LCS(Xi,Yj)\text{LCS}(X_{i},Y_{j})来自顶向上的求解该问题,存储开销较大。可以通过储存LCS(Xi,Yj)\text{LCS}(X_{i},Y_{j})的长度,以及存储每一步是从哪来的标识,来解决这个问题,具体如下:

首先可以得到
c[i,j]={0ij=0c[i1,j1]+1xi=yjmax(c[i1,j],c[i,j1]))xiyj c[i,j]=\begin{cases}0&i*j=0\\c[i-1,j-1]+1&x_i=y_j\\\max(c[i-1,j],c[i,j-1]))& x_i\ne y_j\end{cases}
c[i,j]c[i,j]表示LCS(Xi,Yj)LCS(X_i,Y_j)的长度,其中ij=0i*j=0时,表示空序列与任何序列的最长公共子序列长度为0。

另外为了构造出LCS(X,Y)LCS(X,Y),做一个标识,有
s[i,j]={1ij=0,0xi=yj,1xiyj,c[i1,j]>c[i,j1],LCS(Xi1,Yj)2xiyj,c[i1,j]<c[i,j1]LCS(Xi,Yj1) s[i,j]=\begin{cases}-1&i*j=0,寻找尽头标识\\0&x_i=y_j,标识找到最长公共子序列的元素\\1& x_i\ne y_j,c[i-1,j]>c[i,j-1],标识下一个元素应在LCS(X_{i-1},Y_j)\\2& x_i\ne y_j,c[i-1,j]<c[i,j-1],标识下一个元素应在LCS(X_{i},Y_{j-1})\\\end{cases}
C++实现代码:
代码下载

#include <iostream>
#include <string>
#include <vector>
using namespace std;

string LCS(string X,string Y){
    if(X.size()==0||Y.size()==0) return string();
    int n = X.size()+1,m = Y.size()+1;
    int c[n][m],s[n][m];
    //空序列与任何序列最长为0
    for(int i=0;i<n;++i) {c[i][0] = 0; s[i][0] = -1;}
    for(int i=0;i<m;++i) {c[0][i] = 0; s[0][i] = -1;}

    //根据迭代公式,自底向上更新c,s数组
    for (size_t i = 1; i < n; ++i)
    {
        for (size_t j = 1; j < m; ++j)
        {
            //判断此时序列尾字符是否相等
            if(X[i-1]==Y[j-1]) {
                c[i][j] = c[i-1][j-1]+1;
                s[i][j] = 0;
            }else{
                if(c[i-1][j]>c[i][j-1]){
                    c[i][j] = c[i-1][j];
                    s[i][j] = 1;
                }else{
                    c[i][j] = c[i][j-1];
                    s[i][j] = 2;
                }
            }
        }
        
    }
    
    //通过c,s数组构造LCS
    string ans(c[n-1][m-1],'a');
    --n;--m;
    while(s[n][m]!=-1){
        if(s[n][m]==0) {ans[c[n][m]-1]=X[n-1];--n;--m;}
        else if(s[n][m]==1){--n;}
        else if(s[n][m]==2){--m;}
    }
    return ans;
    
}


int main(){
    string X = "BCADB";
    string Y = "BADB";
    cout<<X<<"与"<<Y<<"的最长公共子序列:"<<endl;
    cout<<LCS(X,Y)<<endl;
    system("pause");
    return 0;
}

Reference

《算法导论》,第十五章

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值