最长公共子序列(LCS)

来源:互联网 发布:servlet修改表单数据 编辑:程序博客网 时间:2024/06/05 06:15

本来这篇文章是想直接转载过来一篇,然后看看就行了,但是个人总觉得看别人的不如自己动手写过的理解好,所以还是决定把理解的过程记录下来。

LCS问题:

首先先知道LCS问题,这有两种:
1.Longest Common Substiring —- 最长公共子串
2.Longest Common Sequence —- 最长公共子序列

这两者的区别是:前者必须是原字符串中连续的一段
后者可以是在原字符串中随意抽取的一些字符串拼凑成的字符串,只需要遵守顺序即可

也就是说:子串字符的位置必须是连续的,子序列不必连续

求两个子串的最长公共子序列

1.暴力法:

假设两个字符串str1.length = m ,str2.length = n

找出m中所有的子序列,找出n中所有的子序列,然后两者中所有元素一一对比
找出过程需要2^m 和 2^n 的时间复杂度,再一一对比。

即时间复杂度是:O(2^m * 2^n)
空间复杂度:O(2^m + 2^n)

2.动态规划

这种类型的题目考的就是动态规划

这里引用July的一段分析,先分析完再来做题:
事实上,最长公共子序列问题也有最优子结构性质。

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的任一最长公共子序列(设其长度为k)的最后一个字符,即是:Zk = Xm = Yn。这就得到:Zk - 1 ∈ LCS(Xm-1 , Yn-1),即Z的前缀Zk-1是Xm-1 与 Ym-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中,至少有一个必然成立,若Zx ≠ 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) )
如下图所示:
这里写图片描述

看完以上的专业的推导,现在来说说咱们自己的理解吧(乡土气息弥漫….),就做这个题目先来:

str1  : B D C A B A          长度为m = 6      str2  : A B C B D A B        长度为n = 7找出这两个字符串中的最长公共子序列
  1. 首先,我们申请一个二维数组dp[m] [n],用来存储两个str分别从1~m与1~n的所有长度的串对应的最长公共子序列的长度。
  2. 初始化一些已知的情况,比如:dp[0][0] = 0,dp[i] [0] = dp[0] [j],显而易见,当一个str长度为0,另一个长度无论你为多少,都没有公共子串,长度也就为0.
  3. 之后的值就取决于如上图中的另外两种情况,当str1[i] = str2[j] 的时候,就考虑对角上的前一个元素,在它的基础上+1,得到当前最大,当前元素的前一个对角元素是str1和str2长度都减去1的最优解(我们从开始就是这么定义的,相等在加1,不等就沿用左或上的最优,因为没有加1,那就找各自在对方不变的情况下回退一步的最优(一个长度减1,一个长度不变),即是最优);;在不等的时候,只需要沿用上一次比较中str1[i-1][j] 和 str2[i][j-1]中比较大的一个,意思就是保留之前子串比较中最优的那一个,因为还没有得到最新的最优嘛(不能+1)。那么无论怎么样,我们都得到了当前的最优结果,供下一次再来选择比较。

具体的过程如图下:
这里写图片描述

代码如下:

int Lcs(string s1,string s2){        int m = s1.length();        int n = s2.length();        vector<vector<int> > res(n+1);        for(int i = 0;i <= n;i++){                res[i].resize(m+1);        }        for(int i = 0;i < m;i++){                for(int j = 0; j < n;j++){                        if(s1[i] == s2[j]){                                res[i+1][j+1] = res[i][j] + 1;                        }                        else{                                res[i+1][j+1] = max(res[i][j+1],res[i+1][j]);                        }                }        }        for(int i = 0;i <= m;i++){                for(int j = 0;j <= n;j++){                        cout<<res[i][j]<<" ";                }                cout<<endl;        }        cout<<endl;        return res[m][n];}

总结:

大概说完这个过程,起码我们能拿到两个字符串立马画出上面这张图吧。
动态规划这种拆分最优子问题的思想个人觉得还是比较难的,尤其是要将一个问题转化成dp,是一件很需要思想的事情,暂时自己的办法是多练,多写,多想,希望在某个时刻脑子开窍了动态规划清晰地像我未来老婆一样在我的脑子里浮现了,有什么错误还请指出,谢谢!

0 0
原创粉丝点击