公共子序列与公共子串问题

1、公共子序列问题

网上有很多关于公共子序列问题,说的大同小异,看了很多不明白,很多都是晦涩难懂,这里分享一个连接,个人觉得讲述的比较明白,易懂。

http://blog.csdn.net/v_july_v/article/details/6695482

我这里也简单的把自己的理解说一下,求公共子序列问题是一个非常常见的问题,最差的方法就是暴力匹配,暴力匹配算法第一步求去短字符串的所有序列组合,然后从长到短一个一个的去匹配时候有公共序列相同,即使使用了这样的剪枝,该算法效率任然很低。

比较受人青睐的算法当然莫过于动态规划了,动态规划的核心是找到转移方程。把复杂的问题通过转移方程转移到子问题。

  • 动态规划算法

事实上,最长公共子序列问题也有最优子结构性质。

记:

Xi=﹤x1,?,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)

Yj=﹤y1,?,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)

假定Z=﹤z1,?,zk﹥∈LCS(X , Y)。

  • xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。
  • xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y),类似的,若zk≠yn 则有Z∈LCS(X , Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。

由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。

也就是说,解决这个LCS问题,你要求三个方面的东西:1、LCS(Xm-1,Yn-1)+1;2、LCS(Xm-1,Y),LCS(X,Yn-1);3、max{LCS(Xm-1,Y),LCS(X,Yn-1)}。

所以解决这个问题的动态转移方程即:

if xm==yn  LCS(Xm,Yn)= LCS(Xm-1,Yn-1)+1;
if xm!=yn LCS(Xm,Yn)=  max{LCS(Xm-1,Yn),LCS(Xm,Yn-1)};

代码如下:

#include <stdio.h>
#include <string.h>

/*
c[i][j]存储的是字串1到i位置,字串2到j位置时公共子序列的最大长度

if str1[i] == str2[j]    c[i][j] = c[i-1][j-1]+1;
if str1[i] != str2[j]    c[i][j] = max{c[i-1][j],c[i][j-1]}
*/
int lcs(char *str1,char *str2,int len1,int len2,int c[100][100])
{
    if (str1 == NULL || str2 ==NULL)
    {
        return -1;//输入字符串错误
    }
    //初始化记录dp的二维数组
    for (int i = 0; i <= len1; i++)
    {
        for (int j = 0; j <= len2; j++)
        {
            c[i][j] = 0;
        }
    }
    //dp运算
    for (int i = 1; i <= len1; i++)
    {
        for (int j = 1; j <= len2; j++)
        {
            if(str1[i-1] == str2[j-1])
            {
                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];
            }
        }
    }
    //打印出dp数组存储的内容
    for (int i = 0; i <= len1; i++)
    {
        for (int j = 0; j <= len2; j++)
        {
            printf("%d ",c[i][j]);
        }
        printf("\n");
    }

    //打印出公共子序列
    char str[100]={0};
    int index = c[len1][len2]-1;
    for (int i = len1,j = len2; i>0&&j>0;)
    {
        if(str1[i-1] == str2[j-1])
        {
            str[index--] = str1[i-1];
            i--;
            j--;
        }
        else
        {
            if(c[i][j-1]>c[i-1][j])
            {
                j--;
            }else
            {
                i--;
            }
        }
    }
    printf("公共子序列为:%s\n",str);
    return c[len1][len2];
}

int main(int argc, char **argv)
{
    char str1[] = {"ABCBDAB"};
    char str2[] = {"BDCABA"};
    int c[100][100];
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    int num = lcs(str1,str2,len1,len2,c);
    printf("公共子序列的长度:%d\n",num);
    return 0;
}

运行结果

2、最大公共子串

首先区分下公共字串和公共子序列的区别,公共子序列是在整个字符串中只要按照顺序可以不用连续的,但是公共子串是指必须连续的字符串,举个例子

ABCBDAB
BDCABA

公共子序列是  BCBA

公共字串是  AB

求公共字串比公共子序列稍微简单了一些,如果上边所述,公共子串也可以用暴力匹配方法,求出较短的字符串的所有子串,然后可以从长到短利用kmp字符串匹配算法求出公共子串,同时还添加了剪枝,但是字样的暴力匹配效率始终是比较差的,最好的方法还是使用动态规划。

根据上边公共子序列动态规划的方法分析,其实我们可以发现公共子串和公共子序列非常类似

只是在状态转移方程是稍有不同,

事实上,最长公共子串问题也有最优子结构性质。

记:

Xi=﹤x1,?,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)

Yj=﹤y1,?,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)

假定Z=﹤z1,?,zk﹥∈LCS(X , Y)。

  • xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子串Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子串。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。
  • 重要的是这里的不同:
    • xm≠yn,由于zk≠xm与zk≠yn 那么说明之前相同的字符串也不能连接起来,此时的LCS(X,Y) 的长度回归到0重新找最长的公共子串。

所以:关于最长公共子串的动态转移方程为:

if xm==yn  LCS(Xm,Yn)= LCS(Xm-1,Yn-1)+1;
if xm!=yn LCS(Xm,Yn)=  0;

代码如下:

#include <stdio.h>
#include <string.h>

/*
c[i][j]存储的是字串1到i位置,字串2到j位置时公共字串的最大长度

if str1[i] == str2[j]    c[i][j] = c[i-1][j-1]+1;
if str1[i] != str2[j]    c[i][j] = 0
*/
int lcs(char *str1,char *str2,int len1,int len2,int c[100][100])
{
    if (str1 == NULL || str2 ==NULL)
    {
        return -1;//输入字符串错误
    }
    //初始化记录dp的二维数组
    for (int i = 0; i <= len1; i++)
    {
        for (int j = 0; j <= len2; j++)
        {
            c[i][j] = 0;
        }
    }
    //dp运算
    int max = -1;
    int col=0,row=0;
    for (int i = 1; i <= len1; i++)
    {
        for (int j = 1; j <= len2; j++)
        {
            if(str1[i-1] == str2[j-1])
            {
                c[i][j]=c[i-1][j-1]+1;
                if(c[i][j]>max)
                {
                    row = i;
                    col = j;
                    max = c[i][j];
                }
            }
            else {
                c[i][j] = 0;
            }
        }
    }
    //打印出dp数组存储的内容
    for (int i = 0; i <= len1; i++)
    {
        for (int j = 0; j <= len2; j++)
        {
            printf("%d ",c[i][j]);
        }
        printf("\n");
    }

    //打印出公共子串
    printf("最长公共子串:");
    for (int i = row-max; i<row;i++)
    {
        printf("%c",str1[i]);
    }
    printf("\n");
    return max;
}

int main(int argc, char **argv)
{
    char str1[] = {"ABCBDAB"};
    char str2[] = {"BDCABA"};
    int c[100][100];
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    printf("字符串1:%s\n",str1);
    printf("字符串2:%s\n",str2);
    int num = lcs(str1,str2,len1,len2,c);
    printf("公共子序列的长度:%d\n",num);
    return 0;
}

结果:

时间: 2024-12-09 08:17:38

公共子序列与公共子串问题的相关文章

1808:公共子序列(公共子序列)

1808:公共子序列 查看 提交 统计 提问 总时间限制:  1000ms 内存限制:  65536kB 描述 我们称序列Z = < z1, z2, ..., zk >是序列X = < x1, x2, ..., xm >的子序列当且仅当存在 严格上升 的序列< i1, i2, ..., ik >,使得对j = 1, 2, ... ,k, 有xij = zj.比如Z = < a, b, f, c > 是X = < a, b, c, f, b, c >

POJ 2250 Compromise(最长公共子序列)

题意:求两段文本的最长公共文本: 思路:最长公共子序列+打印公共序列: #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int dp[505][505],num1,num2; char s[505][505],s1[505][505],s2[505][505]; void lcs(int a,int b) { if(a==0||b==0) return; if(s[a

最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和

参考:http://www.ahathinking.com/archives/124.html 最长公共子序列 1.动态规划解决过程 1)描述一个最长公共子序列 如果序列比较短,可以采用蛮力法枚举出X的所有子序列,然后检查是否是Y的子序列,并记录所发现的最长子序列.如果序列比较长,这种方法需要指数级时间,不切实际. LCS的最优子结构定理:设X={x1,x2,……,xm}和Y={y1,y2,……,yn}为两个序列,并设Z={z1.z2.……,zk}为X和Y的任意一个LCS,则: (1)如果xm=

最长递增子序列 &amp;&amp; 最大子序列、最长递增子序列、最长公共子串、最长公共子序列、字符串编辑距离

http://www.cppblog.com/mysileng/archive/2012/11/30/195841.html 最长递增子序列问题:在一列数中寻找一些数,这些数满足:任意两个数a[i]和a[j],若i<j,必有a[i]<a[j],这样最长的子序列称为最长递增子序列. 设dp[i]表示以i为结尾的最长递增子序列的长度,则状态转移方程为: dp[i] = max{dp[j]+1}, 1<=j<i,a[j]<a[i]. 这样简单的复杂度为O(n^2),其实还有更好的方

最长公共子串和最长公共子序列

最长公共子串与最长公共子序列是有区别的.区别在于最长公共子串要求字符是连续的. 例如:str1:  abcd      str2:  abec那么最长公共子序列是:abc,长度为3最长公共子串是:ab,长度为2 1. 最长公共子序列 Largest common subsequence 最长公共子序列:用f[i][j]表示str1的前i个字符与str2的前j个字符的最长公共子序列的长度. int LCS(int a[],int b[],int len) { vector<int> f(len+

最长公共子串与最长公共子序列之间的关系

最近工作中需要写一个算法,而写完这个算法我却发现了一个很有意思的事情.需要的这个算法是这样的:对于A,B两个字符串,找出最多K个公共子串,使得这K个子串长度和最大.一开始想了一些乱七八糟的想法. 错误想法1:比如每次找最长公共子串,找到一个子串后,从A,B两个字符串中删除这个子串,之后在剩下的串中再找最长公共子串,像这样找K次.但是可惜是错误的. 举个反例: A=KABCDELMABCDEFGNFGHIJK B=KABCDEFGHIJK K=2 按照这种方式选取结果为:ABCDEFG+HIJK,

[Data Structure] LCSs——最长公共子序列和最长公共子串

什么是 LCSs? 什么是 LCSs? 好多博友看到这几个字母可能比较困惑,因为这是我自己对两个常见问题的统称,它们分别为最长公共子序列问题(Longest-Common-Subsequence)和最长公共子串(Longest-Common-Substring)问题.这两个问题非常的相似,所以bbs.chinaacc.com/forum-2-3/topic-5611515.html bbs.chinaacc.com/forum-2-3/topic-5611514.html bbs.chinaac

动态规划算法之:最长公共子序列 & 最长公共子串(LCS)

1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab") b a b c 0 0 0 a 0 1

《算法导论》读书笔记之动态规划—最长公共子序列 &amp; 最长公共子串(LCS)

From:http://my.oschina.net/leejun2005/blog/117167 1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是