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

 

posted @ 2013-08-29 04:47  duanguyuan  阅读(994)  评论(0编辑  收藏  举报