最长公共子序列问题

最长公共子序列问题

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

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

  假设 X = x 1 x 1 x 2 … … x n , Y = y 1 y 1 y 2 … … y m X=x_1x_1x_2……x_n,Y=y_1y_1y_2……y_m X=x1x1x2xn,Y=y1y1y2ym,假设其最长公共子序列为 Z = z 1 z 2 … … z k Z = z_1z_2……z_k Z=z1z2zk

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

  当 x n ≠ y m x_n\ne y_m xn=ym时,可以得到 Z = max ⁡ ( LCS ( X n − 1 , Y m ) , LCS ( X n , Y m − 1 ) ) Z=\max(\text{LCS}(X_{n-1},Y_m),\text{LCS}(X_{n},Y_{m-1})) Z=max(LCS(Xn1,Ym),LCS(Xn,Ym1))(取两者较长的那个)。

  综上得到:
Z = { LCS ( X n − 1 , Y m − 1 ) + x n x n = y m max ⁡ ( LCS ( X n − 1 , Y m ) , LCS ( X n , Y m − 1 ) ) x n ≠ y m 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} Z={LCS(Xn1,Ym1)+xnmax(LCS(Xn1,Ym),LCS(Xn,Ym1))xn=ymxn=ym
上述可以说明这个问题满足最优子结构特征。但是如果通过存储 LCS ( X i , Y j ) \text{LCS}(X_{i},Y_{j}) LCS(Xi,Yj)来自顶向上的求解该问题,存储开销较大。可以通过储存 LCS ( X i , Y j ) \text{LCS}(X_{i},Y_{j}) LCS(Xi,Yj)的长度,以及存储每一步是从哪来的标识,来解决这个问题,具体如下:

首先可以得到
c [ i , j ] = { 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 ≠ y j 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]=0c[i1,j1]+1max(c[i1,j],c[i,j1]))ij=0xi=yjxi=yj
c [ i , j ] c[i,j] c[i,j]表示 L C S ( X i , Y j ) LCS(X_i,Y_j) LCS(Xi,Yj)的长度,其中 i ∗ j = 0 i*j=0 ij=0时,表示空序列与任何序列的最长公共子序列长度为0。

另外为了构造出 L C S ( X , Y ) LCS(X,Y) LCS(X,Y),做一个标识,有
s [ i , j ] = { − 1 i ∗ j = 0 , 寻 找 尽 头 标 识 0 x i = y j , 标 识 找 到 最 长 公 共 子 序 列 的 元 素 1 x i ≠ y j , c [ i − 1 , j ] > c [ i , j − 1 ] , 标 识 下 一 个 元 素 应 在 L C S ( X i − 1 , Y j ) 2 x i ≠ y j , c [ i − 1 , j ] < c [ i , j − 1 ] , 标 识 下 一 个 元 素 应 在 L C S ( X i , Y j − 1 ) 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} s[i,j]=1012ij=0,xi=yj,xi=yj,c[i1,j]>c[i,j1],LCS(Xi1,Yj)xi=yj,c[i1,j]<c[i,j1]LCS(Xi,Yj1)
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

《算法导论》,第十五章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值