动态规划之最长公共子序列问题(LCS)

你离开我真会死。 提交于 2019-12-08 11:33:20

问题描述

输入:
X={x1,x2,,xm}
Y={y1,y2,,ym}

输出:
Z=X和Y的最长公共子序列

说明:
如果:X={A,B,C},Y={A,C,D},则X和Y的最长公共子序列Z={A,C}

结构分析

我们将一个序列如X的前i个元素定义:Xi,则有Xi={x1,x2,,xi}

我们将XY的最长公共子序列记为LCSXY
我们将X的前i个和Y的前j个元素的最长公共子序列记为LCSXiYj

我们看一下如下的关系:
图1

我们假设LCSXmYn=Z={z1,,zk}

我们就可以知道LCSXm1Yn1LCSXmYn之间的关系为:

(1)当Xm=Yn时:

LCSXmYn=LCSXm1Yn1+<xm=yn>

(2)当XmYnzkxm时:

LCSXmYn=LCSXm1Yn

(3)当XmYnzkyn时:

LCSXmYn=LCSXmYn1

建立递推方程

我们定c[i][j]等于LCSXiYj里面元素的个数

我们有以下的递推方程:

(1)当i=0j=0时:

c[i][j]=0

(2)当i>0j>0xi=yj时:

c[i][j]=c[i1][j1]+1

(2)当i>0j>0xiyj时:

c[i][j]=MAX{c[i1][j],c[i][j1]}

自底向上计算LCS

我们现在有一个二维表,抽取其中的c[i1][j1],c[i1][j],c[i][j1],c[i][j]来进行分析,如下图

图2

我们在知道c[i][j]的旁边的c[i1][j1],c[i1][j],c[i][j1]这三个数的值之后就可以得到c[i][j]的值!

我们取一个XY里面元素个数都是4的实例进行分析:

画出如图的二维表,我们目的是求出最右下角的c[4][4]
图3

根据上面的递推方程(1)我们得到如下的数组的值都为0
图6

接下来我们就可以由以上已知的部分依次得到最长公共序列的个数!
比如:c[1][1]可以根据左上半部c[0][0],c[0][1],c[1][0]得到!

现在我们根据上面的方法可以得到相应最长公共子序列的个数了,那么怎样得到这个最长公共子序列呢?
我们只需要从这个二维表的最右下角向左上角回溯即可!详细看代码:

    int num=0;
    int i=xLen;
    int j=yLen;
    //回溯获得最长子序列
    while(i!=0&&j!=0){
        if(c[i][j]!=c[i-1][j]){
            z[++num]=x[i];
            j--;
            i--;
            while(c[i][j]==c[i][j-1]) j--;
        }else{
            i--;
        }
    } 

编程实现

如果觉得上面说的太过于啰嗦,或者我表述的并不是很清晰,那么我们就直接看代码吧!

#include<iostream>
#include<cstring>
#define MAX_LEN 100

using namespace std;

/**
 * @author zjq~
 * @time 2017/07/08
 * @func 动态规划求解最长公共子序列问题
 */

char x[MAX_LEN];        //两个子序列
char y[MAX_LEN];

int xLen=0;         //两个子序列的长度
int yLen=0;

char z[MAX_LEN];        //保存最长公共子序列
int c[MAX_LEN][MAX_LEN];    //记录LCS[i][j] 的个数

void getLCS() {

    //构造二维表 
    for(int i=0; i<=xLen; i++) {
        for(int j=0; j<=yLen; j++) {
            if(i==0||j==0) {
                c[i][j]=0;
            } else if(i>0&&&j>0&&x[i]==y[j]) {
                c[i][j]=c[i-1][j-1]+1;
            } else {
                c[i][j]=c[i-1][j]>c[i][j-1]?c[i-1][j]:c[i][j-1];
            }
        }
    }
    int num=0;
    int i=xLen;
    int j=yLen;
    //回溯获得最长子序列
    while(i!=0&&j!=0){
        if(c[i][j]!=c[i-1][j]){
            z[++num]=x[i];
            j--;
            i--;
            while(c[i][j]==c[i][j-1]) j--;
        }else{
            i--;
        }
    } 
}

int main() {
    cin>>xLen;                  //下标从 i=1 开始
    for(int i=1; i<=xLen; i++) {
        cin>>x[i];
    }
    cin>>yLen;
    for(int i=1; i<=yLen; i++) {
        cin>>y[i];
    }
    getLCS();               //获得最长公共子序列

    cout<<"最长公共子序列包含元素个数为:"<<c[xLen][yLen]<<endl;
    cout<<"最长公共子序列为:";
    for(int i=c[xLen][yLen];i>0;i--){
        cout<<z[i]<<" ";
    }
    cout<<endl;

}

运行结果截图:

运行结果

最后

去九度OJ提交一下看看你的代码是否正确吧!
http://ac.jobdu.com/problem.php?pid=1042
注意一下两点:
使用gets加上头文件#include<cstdio>
OJ上面的题目需要测试多组数据,注意使用while(gets(x)){}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!