zoukankan      html  css  js  c++  java
  • DP:LCS(最长公共子串、最长公共子序列)

    1. 两者区别

    约定:在本文中用 LCStr 表示最长公共子串(Longest Common Substring),LCSeq 表示最长公共子序列(Longest Common Subsequence)。

    子串要求在原字符串中是连续的,而子序列则没有要求。例如:

    字符串 s1=abcde,s2=ade,则 LCStr=de,LCSeq=ade。

    2. 求最长公共子串(LCStr)

    算法描述:构建如下图的矩阵dp[][],当s1[i] == s2[j] 的时候,dp[i][j]=1;最后矩阵中斜对角线上最长的“1”序列的长度,就是 LCStr 的长度。

    但是求矩阵里斜线上的最长的“1”序列,仍然略显麻烦,我们进行如下优化:当要往矩阵中填“1”的时候,我们不直接填“1”,而是填“1”+左上角的那个数。如下图所示:

    这样,我们只需求出矩阵里的最大数(注意:最大数可不一定在最右下角,别误解了上图),即是 LCStr 的长度。

    要求出这个 LCStr,其他的不多说了,见代码中注释。

    C++ code:

     1 #include <iostream>
     2 #include <string>
     3 #include <cstdlib>         // freopen
     4 #include <cstring>         // memset
     5 using namespace std;
     6 
     7 #define MAXN 2001
     8 static int dp[MAXN][MAXN];
     9 
    10 string LCStr(const string &s1, const string &s2)
    11 {
    12     string result;
    13      
    14     //s1纵向,s2横向
    15     //len1行,len2列 
    16     int len1=s1.length(), len2=s2.length();
    17     memset(dp,0,sizeof(dp));
    18     
    19     //预先处理第一行第一列 
    20     for(int i=0; i<len2; ++i)
    21         if(s1[0]==s2[i]) dp[0][i]=1;
    22     for(int i=0; i<len1; ++i)
    23         if(s1[i]==s2[0]) dp[i][0]=1;
    24     
    25     for(int i=1; i<len1; ++i)
    26     for(int j=1; j<len2; ++j)
    27         if(s1[i]==s2[j]) dp[i][j]=dp[i-1][j-1]+1;    //矩阵填充 
    28     
    29     //将第一行的最大值移到最右边 
    30     for(int i=1; i<len2; ++i)
    31         if(dp[0][i]<dp[0][i-1]) dp[0][i]=dp[0][i-1];
    32     
    33     //从第二行开始,将每一行的最大值移到最右边
    34     //最后边的数和上一行的最右边数比较大小,将大的下移
    35     //到最后,右下角的数就是整个矩阵的最大值 
    36     for(int i=1; i<len1; ++i)
    37     {
    38         for(int j=1; j<len2; ++j)
    39             if(dp[i][j]<dp[i][j-1]) dp[i][j]=dp[i][j-1];
    40         if(dp[i][len2-1]<dp[i-1][len2-1]) dp[i][len2-1]=dp[i-1][len2-1];
    41     }
    42     cout<<"length of LCStr: "<<dp[len1-1][len2-1]<<endl;
    43     
    44     int max = dp[len1-1][len2-1];
    45     int pos_x;
    46     for(int i=0; i<len1; ++i)
    47     for(int j=0; j<len2; ++j)
    48     {
    49         if(dp[i][j]==max)
    50         {
    51             pos_x=i;
    52             j=len2;    ///
    53             i=len1;    ///快速跳出循环 
    54         }
    55     }
    56     result=s1.substr(pos_x-max+1,max);
    57     return result;
    58 }
    59 
    60 int main()
    61 {
    62     int t;
    63     freopen("in.txt","r",stdin);
    64     cin>>t;
    65     cout<<"total tests: "<<t<<endl<<endl;
    66     while(t--)
    67     {
    68         string a,b;
    69         cin>>a>>b;
    70         cout<<a<<endl<<b<<endl;
    71         
    72         string res=LCStr(a,b);
    73         cout<<"LCStr: "<<res<<endl<<endl;
    74     }
    75     return 0;
    76 }
    View Code

    运行:

    输入:

    5
    abcde
    ade
    flymouseEnglishpoor
    comeonflymouseinenglish
    BCXCADFESBABCACA
    ABCACADF
    programming
    contest
    123454567811267234678392
    1457890567809713265738

    输出:

    3. 最长公共子序列(LCSeq)

    算法描述:

    矩阵最后的 dp[i][j] 就是 LCSeq 的长度。

    为了把 LCSeq 求出来,我们在给每一个 dp[i][j] 赋值的时候,需要记住这个值来自于哪里。是来自于左上角(LEFTUP),还是上边(UP),还是左边(LEFT)。然后从矩阵最后一个元素回溯,就能找出 LCSeq。如下图:

    当 dp[i-1][j]==dp[i][j-1],即左边的元素等于上边的元素时,我取上边的元素。(取左边的也行,并不影响程序结果。但在整个代码中要统一规则)。

    C++ code:

     1 #include <iostream>
     2 #include <string>
     3 #include <cstring>        //memset
     4 #include <algorithm>    //reverse
     5 #define LEFTUP     0
     6 #define UP        1
     7 #define LEFT    2
     8 #define MAXN 2001
     9 using namespace std;
    10 
    11 //s1纵向,s2横向
    12 int dp[MAXN][MAXN];
    13 short path[MAXN][MAXN];
    14 string LCSeq(const string &s1, const string &s2)
    15 {
    16     int len1=s1.length(), len2=s2.length();
    17     string result="";
    18         
    19     //将dp[][]和path[][]的首行首列清零 
    20     for(int j=0; j<=len2; ++j)
    21         {dp[0][j]=0; path[0][j]=0;}
    22     for(int i=0; i<=len1; ++i)
    23         {dp[i][0]=0; path[i][0]=0;}
    24     //以上代码用 memset 也行 
    25     //memset(dp,0,sizeof(dp));
    26     //memset(path,0,sizeof(path));
    27         
    28     for(int i=1; i<=len1; ++i)
    29     {
    30         for(int j=1; j<=len2; ++j)
    31         {
    32             if(s1[i-1]==s2[j-1])
    33             {
    34                 dp[i][j]=dp[i-1][j-1]+1;
    35                 path[i][j]=LEFTUP;
    36             }
    37             else if(dp[i-1][j]>dp[i][j-1])    //up>=left 这里是用 > 还是 >= ,当LCS不唯一时,对结果有影响,但长度一样 
    38             {
    39                 dp[i][j]=dp[i-1][j];
    40                 path[i][j]=UP;
    41             }
    42             else
    43             {
    44                 dp[i][j]=dp[i][j-1];
    45                 path[i][j]=LEFT;
    46             }
    47         }
    48     } //矩阵填充完成 
    49     cout<<"length of LCSeq: "<<dp[len1][len2]<<endl;
    50     
    51     int i=len1, j=len2;
    52     while(i>0 && j>0)
    53     {
    54         if(path[i][j]==LEFTUP)
    55         {
    56             result+=s1[i-1];
    57             i--;
    58             j--;
    59         }
    60         else if(path[i][j]==UP) i--;
    61         else if(path[i][j]==LEFT) j--;
    62     }
    63     reverse(result.begin(), result.end());
    64     return result;
    65 }
    66 
    67 int main()
    68 {
    69     int t;
    70     freopen("in.txt", "r", stdin);
    71     //freopen("out.txt", "w", stdout);
    72     cin>>t;
    73     cout<<"total tests: "<<t<<endl<<endl;
    74     while(t--)
    75     {
    76         string s1,s2;
    77         cin>>s1>>s2;
    78         cout<<s1<<endl<<s2<<endl;
    79         
    80         string res=LCSeq(s1,s2);
    81         cout<<"LCSeq: "<<res<<endl<<endl;
    82         
    83     }
    84     return 0;    
    85 }        
    86                 
    87     
    View Code

    运行:

    输入:同上

    输出:

    说一下以上程序中37行的 >= 和 > 的区别。当 LCSeq 不唯一时,讨论此区别才有意义。对于以下两个字符串

    s1=BCXCADFESBABCACA  , s2=ABCACADF

    取>=和>符号时的求得的LCSeq分别为:

    BCCADFABCACA

    【s1=BCXCADFESBABCACA  , s2=ABCACADF

    【s1=BCXCADFESBABCACA  , s2=ABCACADF】

    分析:

    当取>=符号时,就是说当dp[i][j]上边的数与左边的数相等时,选择上边的数赋给dp[i][j]。这就造成在后来的回溯过程中,回溯的路径“更快地往上走,更慢的往左走”,当回溯结束时,所求的的子序列由“s2的靠后部分 + s1的靠前部分”构成。(这里的“靠前”、“靠后”为相对而言)。

    当取>符号时,就是说当dp[i][j]上边的数与左边的数相等时,选择左边的数赋给dp[i][j]。这就造成在后来的回溯过程中,回溯的路径“更快地往左走,更慢的往上走”,当回溯结束时,所求的的子序列由“s1的靠后部分 + s2的靠前部分”构成。

    可以参看上面的图来理解这个过程,也可自己画两个图试一下。

    参考:

    维基百科

    http://en.wikipedia.org/wiki/Longest_common_substring_problem

    http://en.wikipedia.org/wiki/Longest_common_subsequence_problem

    http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring

    博客园 

    http://www.cnblogs.com/xudong-bupt/archive/2013/03/15/2959039.html

    推荐:

    http://blog.sina.com.cn/s/blog_54ce19050100wdvn.html

    http://www.cnblogs.com/huangxincheng/archive/2012/11/11/2764625.html

    http://my.oschina.net/leejun2005/blog/117167

    http://www.cnblogs.com/zhangchaoyang/articles/2012070.html

     

  • 相关阅读:
    Linux的目录结构
    python爬虫系列序
    Ant批量处理jmeter脚本
    SoapUI测试webservice接口
    Jmeter分布式部署
    基础知识回顾:闭包
    Jmeter简单应用
    postman安装与使用
    python2.7编码与解码
    Markdown及MarkdownPad使用规则
  • 原文地址:https://www.cnblogs.com/duanguyuan/p/3288285.html
Copyright © 2011-2022 走看看