zoukankan      html  css  js  c++  java
  • 最长公共子序列(POJ1458)

    题目链接:http://poj.org/problem?id=1458

    题目大意:给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。

    输入有若干行,每行是两个字符串。对每一行输入的两个字符串,输出最长公共子串的长度。

    Sample Input
    abcfbc abfcab
    programming contest
    abcd mnp

    Sample Output
    4
    2
    0

    算法分析

    参考1:北大郭炜老师mooc课程
    参考2:http://blog.csdn.net/u013480600/article/details/40741333

    参考3:http://blog.csdn.net/lz161530245/article/details/76943991

    输入两个串s1,s2,
    设MaxLen(i,j)表示:s1的左边i个字符形成的子串,与s2左边的j个字符形成的子串的最长公共子序列的长度(i,j从0开始算)
    MaxLen(i,j) 就是本题的“状态”
    假定 len1 = strlen(s1),len2 = strlen(s2)
    那么题目就是要求 MaxLen(len1,len2)

    显然:
    MaxLen(n,0) = 0 ( n= 0…len1)
    MaxLen(0,n) = 0 ( n=0…len2)
    递推公式:
    if(s1[i-1] == s2[j-1]) //s1的最左边字符是s1[0]
        MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
    else
        MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
    时间复杂度O(mn),其中m,n是两个字串长度。

    关于证明,可以阅读参考2参考3的证明过程。大概过程记录如下:

    我们用Ax表示序列A的连续前x项构成的子序列,即Ax= a1,a2,……ax, By= b1,b2,……by, 我们用LCS(x, y)表示它们的最长公共子序列长度,那原问题等价于求LCS(m,n)。为了方便我们用L(x, y)表示Ax和By的一个最长公共子序列。
    让我们来看看如何求LCS(x, y)。我们令x表示子序列,考虑最后一项
    
    第(1)种情况:Ax = By
    那么它们L(Ax, By)的最后一项一定是这个元素!
    为什么呢?为了方便,我们令t=Ax=By, 我们用反证法:假设L(x,y)最后一项不是t,
    则要么L(x,y)为空序列(别忘了这个),要么L(x,y)的最后一项是Aa=Bb ≠ t, 且显然有a<x,b<y。无论是哪种情况我们都可以把t接到这个L(x,y)后面,从而得到一个更长的公共子序列。矛盾!
    如果我们从序列Ax中删掉最后一项ax得到Ax-1,从序列By中也删掉最后一项by得到By-1,(多说一句角标为0时,认为子序列是空序列),则我们从L(x,y)也删掉最后一项t得到的序列是L(x – 1, y - 1)。为什么呢?和上面的道理相同,如果得到的序列不是L(x - 1, y - 1),则它一定比L(x - 1, y - 1)短,那么它后面接上元素t得到的子序列L(x,y)也比L(x - 1, y - 1)接上元素t得到的子序列短,这与L(x, y)是最长公共子序列矛盾。
    因此L(x,y)=L(x-1,y-1)最后接上元素t,也就是说:
    LCS(Ax, By) = LCS(x - 1, y - 1) + 1
    
    第(2)种情况:Ax ≠ By
    仍然设t=L(Ax,By)的最后一个字符,或者L(Ax,By)是空序列(这时t是未定义值不等于任何值)。
    则t≠Ax和t≠By至少有一个成立,因为t不能同时等于两个不同的值嘛!
    (2.1) 如果t≠Ax,则有L(x,y)=L(x-1,y),因为根本没Ax的事嘛。
           也就是说:LCS(x,y) = LCS(x – 1, y)
    (2.2) 如果t≠By,同理有L(x,y)= L(x,y-1)。
           也就是说:LCS(x,y) = LCS(x, y – 1)
    可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y)=max(LCS(x–1,y),LCS(x,y–1))。
    
    
    看看目前我们已经得到了什么结论:
    LCS(x,y) = 
        (1) LCS(x - 1,y - 1) + 1 如果Ax = By
        (2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By
    这是一个显然的递推式,光有递推可不行,初值是什么呢?
    显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:
    LCS(x,y) = 
        (1) LCS(x - 1,y - 1) + 1     如果Ax = By
        (2) max(LCS(x – 1, y) , LCS(x, y – 1))     如果Ax ≠ By
        (3) 0     如果x=0或者y=0
    
    到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + 1)行(m + 1)列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(n,m)。
    证明过程
     1 #include <iostream>
     2 #include <cstring>
     3 using namespace std;
     4 char sz1[5005];
     5 char sz2[5005];
     6 int maxLen[5005][5005];
     7 int main()
     8 {
     9     while( cin >> sz1 >> sz2 ) 
    10     {
    11         int length1 = strlen( sz1);
    12         int length2 = strlen( sz2);
    13         int nTmp;
    14         int i,j;
    15         for( i = 0;i <= length1; i ++ ) maxLen[i][0] = 0;
    16         for( j = 0;j <= length2; j ++ ) maxLen[0][j] = 0;
    17         for( i = 1;i <= length1;i ++ ) 
    18         {
    19             for( j = 1; j <= length2; j ++ ) 
    20             {
    21                 if( sz1[i-1] == sz2[j-1] )
    22                     maxLen[i][j] = maxLen[i-1][j-1] + 1;
    23                 else
    24                     maxLen[i][j] = max(maxLen[i][j-1],maxLen[i-1][j]);
    25             }
    26         }
    27         cout << maxLen[length1][length2] << endl;
    28     }
    29     return 0;
    30 }

    上面的题目并没有要求输出最长的公共子序列。假如要输出最长公共子序列,可以阅读参考3的代码:(也可以暂时跳过,本文末尾有代码实现。) 

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 int LCSLength(char* str1, char* str2, int **b)
     5 {
     6     int i,j,length1,length2,len;
     7     length1 = strlen(str1);
     8     length2 = strlen(str2);
     9 
    10     //双指针的方法申请动态二维数组
    11     int **c = new int*[length1+1]; //共有length1+1行
    12     for(i = 0; i < length1+1; i++)
    13         c[i] = new int[length2+1];//共有length2+1列
    14 
    15     for(i = 0; i < length1+1; i++)
    16         c[i][0]=0;        //第0列都初始化为0
    17     for(j = 0; j < length2+1; j++)
    18         c[0][j]=0;        //第0行都初始化为0
    19 
    20     for(i = 1; i < length1+1; i++)
    21     {
    22         for(j = 1; j < length2+1; j++)
    23         {
    24             if(str1[i-1]==str2[j-1])//由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素
    25             {
    26                 c[i][j]=c[i-1][j-1]+1;
    27                 b[i][j]=0;          //输出公共子串时的搜索方向
    28             }
    29             else if(c[i-1][j]>c[i][j-1])
    30             {
    31                 c[i][j]=c[i-1][j];
    32                 b[i][j]=1;
    33             }
    34             else
    35             {
    36                 c[i][j]=c[i][j-1];
    37                 b[i][j]=-1;
    38             }
    39         }
    40     }
    41     /*
    42     for(i= 0; i < length1+1; i++)
    43     {
    44     for(j = 0; j < length2+1; j++)
    45     printf("%d ",c[i][j]);
    46     printf("
    ");
    47     }
    48     */
    49     len=c[length1][length2];
    50     for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组
    51         delete[] c[i];
    52     delete[] c;
    53     return len;
    54 }
    55 void PrintLCS(int **b, char *str1, int i, int j)
    56 {
    57     if(i==0 || j==0)
    58         return ;
    59     if(b[i][j]==0)
    60     {
    61         PrintLCS(b, str1, i-1, j-1);//从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串
    62         printf("%c",str1[i-1]);//c[][]的第i行元素对应str1的第i-1个元素
    63     }
    64     else if(b[i][j]==1)
    65         PrintLCS(b, str1, i-1, j);
    66     else
    67         PrintLCS(b, str1, i, j-1);
    68 }
    69 
    70 int main(void)
    71 {
    72     char str1[100],str2[100];
    73     int i,length1,length2,len;
    74     printf("请输入第一个字符串:");
    75     gets(str1);
    76     printf("请输入第二个字符串:");
    77     gets(str2);
    78     length1 = strlen(str1);
    79     length2 = strlen(str2);
    80     //双指针的方法申请动态二维数组
    81     int **b = new int*[length1+1];
    82     for(i= 0; i < length1+1; i++)
    83         b[i] = new int[length2+1];
    84     len=LCSLength(str1,str2,b);
    85     printf("最长公共子序列的长度为:%d
    ",len);
    86     printf("最长公共子序列为:");
    87     PrintLCS(b,str1,length1,length2);
    88     printf("
    ");
    89     for(i = 0; i < length1+1; i++)//释放动态申请的二维数组
    90         delete[] b[i];
    91     delete[] b;
    92     system("pause");
    93     return 0;
    94 }
    求最长公共子序列长度并输出最长公共子序列

     查找并输出最长公共子序列也可以参考https://wenku.baidu.com/view/7e96c94f2b160b4e767fcfc9.html

    空间上的优化:

    观察上面算法中的关键代码:

    for( i = 1;i <= length1;i ++ ) 
    {
        for( j = 1; j <= length2; j ++ ) 
        {
            if( sz1[i-1] == sz2[j-1] ) maxLen[i][j] = maxLen[i-1][j-1] + 1;
            else  maxLen[i][j] = max(maxLen[i][j-1],maxLen[i-1][j]);
        }
    }

    可以发现,计算maxLen数组第i行时用到的只有第i行与第i-1行。我们的目的是要计算maxLen[length1][length2],所以,可以考虑只保存两行即可,也就是使用滚动数组只保存两行。

     代码如下:(参考来源

    cur表示当前需要求的那一行的下标。

     1 #include <iostream>
     2 #include <cstring>
     3 using namespace std;
     4 char sz1[5005];
     5 char sz2[5005];
     6 int maxLen[2][5005];
     7 int main()
     8 {
     9     int i,j,length1,length2,cur=0;
    10     
    11     while( cin >> sz1 >> sz2 ) 
    12     {
    13         length1 = strlen( sz1);
    14         length2 = strlen( sz2);
    15         for( i=0;i<2; i++ ) maxLen[i][0]=0;
    16         for( j=0;j<=length2;j++ ) maxLen[0][j]=0;
    17         cur=0;
    18         
    19         for( i = 1;i <= length1;i ++ ) 
    20         {
    21             cur ^= 1;
    22             for( j = 1; j <= length2; j ++ ) 
    23             {
    24                 if( sz1[i-1] == sz2[j-1] )
    25                     maxLen[cur][j] = maxLen[cur^1][j-1] + 1;
    26                 else
    27                     maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
    28             }
    29         }
    30         cout << maxLen[cur][length2] << endl;
    31     }
    32     return 0;
    33 }
    View Code

    下面修改一下代码寻找出一个最长公共子序列。 

    上面经过空间优化后,也只是寻找到了最长公共子序列的长度,那么如何得到一个最长公共子序列而仅仅不是简单的长度呢?其实我们离真正的答案只有一步之遥!

     

     

     参考上图,我们建立一个二维数组ans[][],在寻找最长公共子序列的长度时用ans[i][j]记录LCS(i,j)是如何来的(从左边、上边或是从左上),ans[i][j]等于1,2,3分别表示:

    L(x,y) = L(x, y – 1) 

    L(x,y)= L(x – 1, y)

    L(x,y) = L(x,- 1 y- 1)末尾接上Ax 

    当ans[i][j]等于3时字符串1的第i个字符(或字符串2的第j个字符,其实两者相同)肯定是最长公共子序列的一部分,要保留到temp[ ]中。所以从ans[][]右下角逆推即可求出temp[ ],然后逆序输出temp[]即可。代码如下:

     1 //51Nod动态规划教程例题 求最长公共子序列的长度并输出一个最长公共子序列
     2 #include <iostream>
     3 #include <cstring>
     4 using namespace std;
     5 #define maxN 5005
     6 char sz1[maxN];
     7 char sz2[maxN];
     8 int maxLen[2][maxN];
     9 char ans[maxN][maxN]={0};
    10 
    11 void printLCS(int len1,int len2);//输出一个最长公共子序列 
    12 int main()
    13 {
    14     int i,j,length1,length2,cur=0;
    15     freopen("poj1458.in","r",stdin);
    16     while( cin >> sz1 >> sz2 ) 
    17     {
    18         memset(ans,0,sizeof(char)*maxN*maxN);
    19         length1 = strlen( sz1);
    20         length2 = strlen( sz2);
    21         for( i=0;i<2; i++ ) maxLen[i][0]=0;
    22         for( j=0;j<=length2;j++ ) maxLen[0][j]=0;
    23         cur=0;
    24         
    25         for( i = 1;i <= length1;i ++ ) 
    26         {
    27             cur ^= 1;
    28             for( j = 1; j <= length2; j ++ ) 
    29             {
    30                 if( sz1[i-1] == sz2[j-1] )
    31                 {
    32                     maxLen[cur][j] = maxLen[cur^1][j-1] + 1;
    33                     ans[i][j]=3;
    34                 }
    35                 else
    36                 {
    37                     //maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
    38                     if(maxLen[cur][j-1]>maxLen[cur^1][j])
    39                     {
    40                         maxLen[cur][j]=maxLen[cur][j-1];
    41                         ans[i][j]=1;
    42                     }
    43                     else
    44                     {
    45                         maxLen[cur][j]=maxLen[cur^1][j];
    46                         ans[i][j]=2;
    47                     }
    48                 }
    49             }
    50         }
    51         cout << maxLen[cur][length2] << endl;
    52         if(maxLen[cur][length2]>0) printLCS(length1,length2);
    53     }
    54     return 0;
    55 }
    56 void printLCS(int len1,int len2)//输出一个最长公共子序列
    57 {
    58     char temp[maxN];
    59     int i=len1,j=len2,k=0;
    60     while(ans[i][j]!=0)
    61     {
    62         if(ans[i][j]==3) { temp[k++]=sz1[i-1]; i--;j--; }
    63         else if(ans[i][j]==1)
    64         {
    65             j--;
    66         }
    67         else if(ans[i][j]==2)
    68         {
    69             i--;
    70         }
    71     }
    72     for(k--;k>=0;k--) printf("%c",temp[k]);
    73     printf("
    ");
    74 }
  • 相关阅读:
    BEC listen and translation exercise 44
    中译英12
    BEC listen and translation exercise 43
    中译英11
    BEC listen and translation exercise 42
    中译英10
    BEC listen and translation exercise 41
    中译英9
    BEC listen and translation exercise 40
    中译英8
  • 原文地址:https://www.cnblogs.com/huashanqingzhu/p/7423745.html
Copyright © 2011-2022 走看看