动态规划 最常公共子序列问题

动态规划

前面一篇博文动态规划 矩阵连乘问题,学习了什么是动态规划,以及什么时候该用动态规划,总得来说就是:
当一个问题可以被分成若干个子问题求解,且子问题可以优化子结构,存在子问题重复的时候,就可以使用动态子结构。

最长公共子序列问题

最长公共子序列的定义:

这里写图片描述

我们来考虑一下如何分为子问题

设 两个序列分别为 X = {x1, x2, x3 …….xm}, Y = {y1, y2, y3 ……yn},他们的最长公共子序列为 Z = {z1, z2, z3 ….zk},那么就存在下面这几种情况:

  1. 如果xm == yn == zk , 那么 Z = {z1, z2…..zk-1} 一定是 X = {x1, x2, x3 …….xm-1}, Y = {y1, y2, y3 ……yn-1}的最长公共子序列;
  2. 如果xm != yn, 且zk != xm, 那么Z = {z1, z2…..zk} 是 X = {x1, x2, x3 …….xm-1}, Y = {y1, y2, y3 ……yn} 的最长公共子序列;
  3. 如果xm != yn, 且zk != yn, 那么Z = {z1, z2…..zk} 是 X = {x1, x2, x3 …….xm}, Y = {y1, y2, y3 ……yn-1} 的最长公共子序列;

并且我们可以看到存在子问题重叠的问题

这里写图片描述

如果不太理解,可以具体代入。

这样的话,当我们问题如果遇到xm == yn的情况下,就直接解决了一个子问题,说明xm或者yn就是最长公共子序列中的最后一个,接下来我们就只需要求X = {x1, x2, x3 …….xm-1}, Y = {y1, y2, y3 ……yn-1}的序列;当xm != yn的情况下,则需要解决两个子问题,分别是zk != xm和 zk != yn的情况,然后取两种情况分别所得的最长公共子序列的最大致。

概念如果有点模糊的话,我们来举个例子:
1. 如果X = “ABCD”,Y = “ACBD”,由于xm = “D” 和 yn = “D”相等,所以我们已经得出XY的最大公共子序列的最后一个值是D;
2. 如果X = “ABDC”,Y = “ACBD”,由于xm = “C”和 yn = “D”不相等,所以我们要解决X = “ABD”,Y = “ACBD”和 X = “ABDC”,Y = “ACB”两个子问题,取两个子问题中最长的公共子序列。

递归方程

这里写图片描述

输出最长公共子序列

除了用c[i, j]来记录i到j的最长公共子序列的长度,还要用一个数组b[i][j]来记录每一个子问题的前一个子问题,然后回溯输出最长公共子序列。

这里写图片描述
(图从网上摘得,如果作者不允许使用,请告知)

代码实现

#include<cstdio>
#include<cstring>

using namespace std;

const int MAXLEN = 10000;

int b[MAXLEN][MAXLEN];
int c[MAXLEN][MAXLEN];

void LCSLength(char* x, char* y, int m, int n){
    //初始化0边界
    int i;
    for(i = 0; i <= m; i++){
        c[i][0] = 0;
    }
    for(i = 1; i <= n; i++){
        c[0][i] = 0;
    }
    for(i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++){
            //用0表示左上,1表示左,2表示上 
            if(x[i-1] == y[j-1]){
                c[i][j] = c[i-1][j-1] + 1;
                b[i][j] = 0;
            }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] = 2;
            }
        }
    }

    //直观看看c数组和b数组 
    for(i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    printf("\n");
    for(i = 0; i < m; i++){
        for(int j = 0; j < n; j++){
            printf("%d ", c[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

void printLCS(char *x, int i, int j)
{
    if(i == 0 || j == 0)
        return;
    if(b[i][j] == 0)
    {
        printLCS(x, i-1, j-1);
        printf("%c ", x[i-1]);
    }
    else if(b[i][j] == 1)
        printLCS(x, i-1, j);
    else
        printLCS(x, i, j-1);
}

int main(){
    char x[MAXLEN] = {"ABCEOG"};
    char y[MAXLEN] = {"BAEOGA"};

    int m = strlen(x);
    int n = strlen(y);

    LCSLength(x, y, m, n);
    printLCS(x, m, n);
    return 0;
}

结果如下:

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值