最长公共子序列(LCS)问题 Longest Common Subsequence 与最长公告字串 longest common substr

问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。

考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:

(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;

(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;

(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。

这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。

求解:

引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i] = Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。

问题的递归式写成:

回溯输出最长公共子序列过程:

算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。

http://blog.csdn.net/yysdsyl/article/details/4226630

一切没有code的分析都是耍流氓。。。 上code

void printLCS(string str1, string str2, vector<vector<int> >flag, int idx1, int idx2)
{

    if(idx1 == 0 || idx2 == 0 )
        return;
    if(flag[idx1][idx2] == 1)
    {
        printLCS(str1, str2,flag, idx1-1, idx2-1);
        cout << idx1 <<"\t"<< idx2 <<"\t";
        cout << str1[idx1-1] <<"\t"<<endl;
    }
    else if(flag[idx1][idx2] == 2)
        printLCS(str1, str2,flag, idx1, idx2-1);
    else if(flag[idx1][idx2] == 3)
        printLCS(str1, str2,flag, idx1-1, idx2);
}

int lcs(string str1, string str2)
{
    const size_t len1 = str1.size();
    const size_t len2 = str2.size();

    if(len1 == 0 || len2 == 0)
        return 0;

    int f[len1 + 1][len2 + 1];
    vector<vector<int> >flag;
    vector<int> tmp;
    tmp.resize(len2+1);
    for(size_t i = 0; i<= len1; i++)
        flag.push_back(tmp);

    //memset(flag,0,sizeof(flag)); // 1: leftup; 2: left; 3: up
    for(size_t i = 0; i <= len1; i++)
    {
        f[i][0] = 0;
    }
    for(size_t i = 0; i <= len2; i++)
    {
        f[0][i] = 0;
    }

    for(size_t i = 1; i <= len1; i++)
    {
        for(size_t j = 1; j <= len2; j++)
        {
            if(str1[i-1] == str2[j-1])
            {
                f[i][j] = f[i-1][j-1] + 1;
                flag[i][j] = 1;
            }
            else
            {
                f[i][j] = max(f[i][j-1], f[i-1][j]);
                if(f[i][j-1] > f[i-1][j])
                    flag[i][j] = 2;
                else
                    flag[i][j] = 3;
            }
        }
    }
#if 0
    for(size_t i = 1; i <= len1; i++)
    {
        for(size_t j = 1; j <= len2; j++)
        {
            //cout << "f["<<j<<"][" <<i<<"]" << f[j][i] <<"\n";
            cout << f[i][j] <<"\t";
        }
        cout << endl;
    }
    cout << endl;
    for(size_t i = 1; i <= len1; i++)
    {
        for(size_t j = 1; j <= len2; j++)
        {
            //cout << "f["<<j<<"][" <<i<<"]" << f[j][i] <<"\n";
            cout << flag[i][j] <<"\t";
        }
        cout << endl;
    }
#endif
    printLCS(str1, str2, flag, len1, len2);
    return f[len1][len2];

}

最长公共字串:

找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  1

a  0  1  0

我们看矩阵的斜对角线最长的那个就能找出最长公共子串。

不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1。

   b  a  b

c  0  0  0

a  0  1  0

b  1  0  2

a  0  2  0

这样矩阵中的最大元素就是 最长公共子串的长度。

在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。

与Subsequence问题不同的是,Substring问题不光要求下标序列是递增的,还要求每次

递增的增量为1, 即两个下标序列为:

<i, i+1, i+2, ..., i+k-1> 和 <j, j+1, j+2, ..., j+k-1>

类比Subquence问题的动态规划解法,Substring也可以用动态规划解决,令

c[i][j]表示Xi和Yi的最大Substring的长度,比如

X = <y, e, d, f>

Y = <y, e, k, f>

c[1][1] = 1

c[2][2] = 2

c[3][3] = 0

c[4][4] = 1

 动态转移方程为:

   如果xi == yj, 则 c[i][j] = c[i-1][j-1]+1

   如果xi ! = yj,  那么c[i][j] = 0

最后求Longest Common Substring的长度等于

max{  c[i][j],  1<=i<=n, 1<=j<=m}

完整的代码如下:

/**
找出两个字符串的最长公共连续子串的长度
** author :liuzhiwei
** data   :2011-08-16
**/
#include "stdio.h"
#include "string.h"
#include "stdlib.h"

int longest_common_substring(char *str1, char *str2)
{
    int i,j,k,len1,len2,max,x,y;
    len1 = strlen(str1);
    len2 = strlen(str2);
    int **c = new int*[len1+1];
    for(i = 0; i < len1+1; i++)
        c[i] = new int[len2+1];
    for(i = 0; i < len1+1; i++)
        c[i][0]=0;        //第0列都初始化为0
    for(j = 0; j < len2+1; j++)
        c[0][j]=0;        //第0行都初始化为0
    max = -1;
    for(i = 1 ; i < len1+1 ; i++)
    {
        for(j = 1; j < len2+1; j++)
        {
            if(str1[i-1]==str2[j-1])     //只需要跟左上方的c[i-1][j-1]比较就可以了
                c[i][j]=c[i-1][j-1]+1;
            else                         //不连续的时候还要跟左边的c[i][j-1]、上边的c[i-1][j]值比较,这里不需要
                c[i][j]=0;
            if(c[i][j]>max)
            {
                max=c[i][j];
                x=i;
                y=j;
            }
        }
    }

    //输出公共子串
    char s[1000];
    k=max;
    i=x-1,j=y-1;
    s[k--]=‘\0‘;
    while(i>=0 && j>=0)
    {
        if(str1[i]==str2[j])
        {
            s[k--]=str1[i];
            i--;
            j--;
        }
        else       //只要有一个不相等,就说明相等的公共字符断了,不连续了
            break;
    }
    printf("最长公共子串为:");
    puts(s);
    for(i = 0; i < len1+1; i++)         //释放动态申请的二维数组
        delete[] c[i];
    delete[] c;
    return max;
}
int main(void)
{
    char str1[1000],str2[1000];
    printf("请输入第一个字符串:");
    gets(str1);
    printf("请输入第二个字符串:");
    gets(str2);
    int len = longest_common_substring(str1, str2);
    printf("最长公共连续子串的长度为:%d\n",len);
    system("pause");
    return 0;
}
时间: 2024-10-11 21:27:19

最长公共子序列(LCS)问题 Longest Common Subsequence 与最长公告字串 longest common substr的相关文章

[2016-05-09][51nod][1006 最长公共子序列Lcs]

时间:2016-05-09 21:12:54 星期一 题目编号:[2016-05-09][51nod][1006 最长公共子序列Lcs] 题目大意:[2016-05-09][51nod][1006 最长公共子序列Lcs].md 分析:动态规划 dp[i][j] 表示字符串A以第i个位置 ,字符串B以第j个位置的最长公共子序列的长度 dp[i][j] = dp[i - 1][j - 1] + 1 if a[i] == a[j] else dp[i][j] == max(dp[i - 1][j] ,

POJ 1458 Common Subsequence(最长公共子序列LCS)

POJ1458 Common Subsequence(最长公共子序列LCS) http://poj.org/problem?id=1458 题意: 给你两个字符串, 要你求出两个字符串的最长公共子序列长度. 分析: 本题不用输出子序列,非常easy,直接处理就可以. 首先令dp[i][j]==x表示A串的前i个字符和B串的前j个字符的最长公共子序列长度为x. 初始化: dp全为0. 状态转移: IfA[i]==B[j] then dp[i][j]= dp[i-1][j-1]+1 else dp[

序列最的问题之最长公共子序列LCS

在程序设计竞赛中,我们时常会遇到序列求最值的问题.在讲今天的问题之前,先小小的说明一下,子序列与子串的问题. 子序列:在原序列中不一定连续: 子串:在原序列中必须连续. 接下来,就开始今天要讲的最长公共子序列LCS(Longest Common Subsequence).对于LCS这一类的问题,一般是相对于两个序列而言,str[]与ch[].先假设str的长度为n,ch的长度为m.假设str[]="ASBDAH",ch[]="SDAAH";其中"SDA&q

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

第一部分.什么是动态规划算法 ok,咱们先来了解下什么是动态规划算法. 动态规划一般也只能应用于有最优子结构的问题.最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似).简单地说,问题能够分解成子问题来解决. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方式计算最优解的值   //此3步构成动态规划解的基础. 由计算出的结果构造一个最优解.   //此步如果只要求计算最优解的值时,可省略. 好,接下来,咱们

编程算法 - 最长公共子序列(LCS) 代码(C)

最长公共子序列(LCS) 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定两个字符串s,t, 求出这两个字符串最长的公共子序列的长度. 字符串的子序列并一定要连续, 能够包含间隔. 即最长公共子序列问题(LCS, Longest Common Subsequence) 使用动态规划, 假设字符相等, 两个字符串就依次递增一位, 一直到字符串的结尾. 代码: /* * main.cpp * * Created on: 2014.7.17

算法重拾之路——最长公共子序列(LCS)

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 第二章:动态规划 最长公共子序列 算法描述: 一个给定序列的子序列是该序列中删去若干元素后得到的序列.确切的说,若给定序列 X={ x1,x2,...,xm },则另一序列 Z = { z1,z2, ... ,zk },是X的子序列是指存在一个严格递增下标序列

1006 最长公共子序列Lcs

1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Input示例 abcicba abdkscab Outpu

51nod 1006 最长公共子序列Lcs(dp+string,无标记数组实现)

1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 取消关注 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Input示

求三个字符串的最长公共子序列LCS(A,B,C)

LCS(A,B,C)!=LCS(A,LCS(B,C)) 反例: abcd abcde abced LCS(B,C)求出来可能是abce或者abcd dp[i][j][k]表示A[0...i],B[0...j],C[0...k]的LCS 转移方程: if (a[i]==b[j]&&b[j]==c[k]) dp[i][j][k]=dp[i-1][j-1][k-1]+1; else dp[i][j][k]=max(max(dp[i][j][k], dp[i-1][j][k]), max(dp[i

动态规划-最长公共子序列LCS

0 问题 给定两个字符串,求最长公共子序列LCS. 也就是说两个字符串中都有的部分,或者理解为,两个字符串同时都删除字符串中的某些字符,使得最终的两个字符串,相等,且是最长的. 1 分析 假设两个str1,str2字符串,已经知道了最长公共子序列长度为L 那么,当在str1和str2,两个的尾部,同时添加一个相同的字符,比如a,那么新的str1,和str2的最长公共子序列长度就是L+1 当str1后面添加一个字符,str2不添加,那么最长公共子序列长度为L 反之,str1不添加,str2添加,那