【第22期】观点:IT 行业加班,到底有没有价值?

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

原创 2015年11月20日 17:29:17

最长公共子序列问题(Longest-common-subsequence Problem)简称LCS问题。题目为给定两个序列X、Y求它们的LCS(最长公共子序列),这里的子序列Z的定义为:Z中的元素既在X中也在Y中,并且他们在X、Y中满足严格的下标为一个增序列(假设下标从左到右依次增大)。另外,不要求Z中的元素在X、Y中是连续的,比如当A = {A, B, C, D, B, C, A},B = {B, C, A, D}时,可以有Z = {B, C, D}。现在我们需要求的是LCS,即符合这种条件的最长的一个公共子序列。

而LCS问题是一类动态规划(DP:dynamic programming)问题 。其具有最优子结构性质,定义如下:

设序列 X = {x1,x2,...,xm} 和 Y = {y1,y2,...yn} 的一个最长公共子序列 Z = {z1,z2,...,zk},则:
    1> 若 xm=yn,则 zk=xm=yn,且Zk-1是Xm-1和Yn-1的最长公共子序列; 
  2> 若 xm≠yn且 zk≠xm ,则 Z是 Xm-1和 Y的最长公共子序列;
    3> 若 xm≠yn且 zk≠yn ,则 Z是 X和 Yn-1的最长公共子序列;
    其中Xm-1={x1,x2,...,xm-1},Yn-1={y1,y2,...yn-1},Zk-1={z1,z2,...,zk-1}。

上面的结论很容易证明。


下面贴上算法导论中的伪代码(其中的数组b可以不用理会,c用于存储Xm和Yn的LCS):


源代码如下(注意:我的源代码中的n是序列X的长度,m是序列Y的长度):

/*  计算LCS,动态规划从前往后推算  */
#include <iostream>
#include <stdio.h>
#include <memory.h>
#define N 1000
char X[N], Y[N];                //序列X、Y
int C[N][N];                    //Cij为Xi和Yj的最长公共子序列
int lcs(int n, int m);          //计算最长公共子序列
void getAnswer(int n, int m);   //构建答案

int main()
{
    int m, n;
    printf("Enter n and m: ");
    while(2 == scanf("%d%d", &n, &m))
    {
        printf("Enter %d elements for X:", n);
        while(getchar() != '\n') continue;      //清除回车符等
        for(int i = 1; i <= n; ++i)
            scanf("%c", &X[i]);
        printf("Enter %d elements for Y:", m);
        while(getchar() != '\n') continue;
        for(int i = 1; i <= m; ++i)
            scanf("%c", &Y[i]);
        printf("MaxLength: %d\nLCS:", lcs(n, m));
        getAnswer(n, m);
        printf("\nEnter n and m: ");
    }
    return 0;
}

int lcs(int n, int m)
{
    for(int i = 0; i <= n; ++i)
        C[i][0] = 0;
    for(int i = 0; i <= m; ++i)
        C[0][i] = 0;

    for(int i = 1; i <= n; ++i)
    {
        for(int j = 1; j <= m; ++j)
        {
            if(X[i] == Y[j])
                C[i][j] = C[i-1][j-1] + 1;
            else if(C[i-1][j] >= C[i][j-1])
                C[i][j] = C[i-1][j];
            else
                C[i][j] = C[i][j-1];
        }
    }
    return C[n][m];
}

void getAnswer(int n, int m)
{
    if(0 == n || 0 == m)
        return;
    if((C[n][m] == C[n-1][m-1] + 1) && (X[n] == Y[m]))  //一定要判断X[n]和Y[m]是否相等
    {
        getAnswer(n-1, m-1);
        printf("%c", X[n]);     //等价于printf("%c", Y[m]);
    }
    else if(C[n][m] == C[n-1][m])
        getAnswer(n-1, m);
    else
        getAnswer(n, m-1);
}


另一种方法是从后面向前面推算,当然得到的答案也肯能不一样,但一定是最长的:

/*  计算LCS,动态规划从后往前推算  */
#include <iostream>
#include <stdio.h>
#include <memory.h>
#define N 1000
char X[N], Y[N];                                //序列X、Y
int C[N][N];                                    //Cij为Xi和Yj的最长公共子序列
int lcs(int n, int m);                          //计算最长公共子序列
void getAnswer(int x, int y, int n, int m);     //构建答案

int main()
{
    int m, n;
    printf("Enter n and m: ");
    while(2 == scanf("%d%d", &n, &m))
    {
        printf("Enter %d elements for X:", n);
        while(getchar() != '\n') continue;      //清除回车符等
        for(int i = 1; i <= n; ++i)
            scanf("%c", &X[i]);
        printf("Enter %d elements for Y:", m);
        while(getchar() != '\n') continue;
        for(int i = 1; i <= m; ++i)
            scanf("%c", &Y[i]);
        printf("MaxLength: %d\nLCS:", lcs(n, m));
        getAnswer(1, 1, n, m);
        printf("\nEnter n and m: ");
    }
    return 0;
}

int lcs(int n, int m)
{
    for(int i = 1; i <= n + 1; ++i)
        C[i][0] = 0;
    for(int i = 1; i <= m + 1; ++i)
        C[0][i] = 0;

    for(int i = n; i >= 1; --i)
    {
        for(int j = m; j >= 1; --j)
        {
            if(X[i] == Y[j])
                C[i][j] = C[i+1][j+1] + 1;
            else if(C[i+1][j] >= C[i][j+1])
                C[i][j] = C[i+1][j];
            else
                C[i][j] = C[i][j+1];
        }
    }
    return C[1][1];
}

void getAnswer(int x, int y, int n, int m)
{
    if(x == n + 1 || y == m + 1)
        return;
    if((C[x][y] == C[x+1][y+1] + 1) && (X[x] == Y[y]))
    {
        printf("%c", X[x]);     //等价于printf("%c", Y[y]);
        getAnswer(x+1, y+1, n, m);
    }
    else if(C[x][y] == C[x+1][y])
        getAnswer(x+1, y, n, m);
    else
        getAnswer(x, y+1, n, m);
}


如有疏漏,请批评指正

版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

算法知识之最长公共子序列问题(动态规划)

关于动态规划的最长公共子序列的问题希望对大家有所帮助,同时也帮助自己回顾该知识点. 一.最长公共子序列的定义 二.最优子结构性质 三.动态规划方法分析 四.问题的提出与解决 五.问题的升华与解决 最后...

动态规划算法解最长公共子序列LCS问题

动态规划算法解LCS问题 作者 July 二零一零年十二月三十一日 本文参考:微软面试100题系列V0.1版第19、56题、算法导论、维基百科。 第一部分...

程序员升职加薪指南!还缺一个“证”!

CSDN出品,立即查看!

三、动态规划算法解最长公共子序列LCS问题(2011.12.13重写)

动态

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

转:http://blog.csdn.net/yysdsyl/article/details/4226630
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)