lcs算法详解.docx
- 文档编号:24349709
- 上传时间:2023-05-26
- 格式:DOCX
- 页数:14
- 大小:151.03KB
lcs算法详解.docx
《lcs算法详解.docx》由会员分享,可在线阅读,更多相关《lcs算法详解.docx(14页珍藏版)》请在冰豆网上搜索。
lcs算法详解
程序员编程艺术第十一章:
最长公共子序列(LCS)问题
0、前言
程序员编程艺术系列重新开始创作了(前十章,请参考程序员编程艺术第一~十章集锦与总结)。
回顾之前得前十章,有些代码就是值得商榷得,因当时得代码只顾阐述算法得原理或思想,所以,很多得与代码规范相关得问题都未能做到完美。
日后,会着力修缮之。
S3Z23。
搜遍网上,讲解这个LCS问题得文章不计其数,但大多给读者一种并不友好得感觉,稍感晦涩,且代码也不够清晰。
本文力图避免此些情况。
力保通俗,阐述详尽。
同时,经典算法研究系列得第三章(三、dynamicprogramming)也论述了此LCS问题。
有任何问题,欢迎不吝赐教。
i6C1E。
第一节、问题描述
什么就是最长公共子序列呢?
好比一个数列 S,如果分别就是两个或多个已知数列得子序列,且就是所有符合此条件序列中最长得,则S 称为已知序列得最长公共子序列。
PyiEk。
举个例子,如:
有两条随机序列,如13455,and245576,则它们得最长公共子序列便就是:
455。
eLUdt。
注意最长公共子串(LongestmonSubstring)与最长公共子序列(LongestmonSubsequence,LCS)得区别:
子串(Substring)就是串得一个连续得部分,子序列(Subsequence)则就是从不改变序列得顺序,而从序列中去掉任意得元素而获得得新序列;更简略地说,前者(子串)得字符得位置必须连续,后者(子序列LCS)则不必。
比如字符串acdfg同akdfc得最长公共子串为df,而她们得最长公共子序列就是adf。
LCS可以使用动态规划法解决。
下文具体描述。
0xrQc。
第二节、LCS问题得解决思路
∙穷举法
解最长公共子序列问题时最容易想到得算法就是穷举搜索法,即对X得每一个子序列,检查它就是否也就是Y得子序列,从而确定它就是否为X与Y得公共子序列,并且在检查过程中选出最长得公共子序列。
X与Y得所有子序列都检查过后即可求出X与Y得最长公共子序列。
X得一个子序列相应于下标序列{1,2,…,m}得一个子序列,因此,X共有2m个不同子序列(Y亦如此,如为2^n),从而穷举搜索法需要指数时间(2^m*2^n)。
AmfhS。
∙动态规划算法
事实上,最长公共子序列问题也有最优子结构性质。
记:
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)。
CA4GJ。
∙若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)得长度}。
g2hUh。
由于上述当xm≠yn得情况中,求LCS(Xm-1,Y)得长度与LCS(X,Yn-1)得长度,这两个问题不就是相互独立得:
两者都需要求LCS(Xm-1,Yn-1)得长度。
另外两个序列得LCS中包含了两个序列得前缀得LCS,故问题具有最优子结构性质考虑用动态规划法。
srS7N。
也就就是说,解决这个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)}。
om8Gr。
行文至此,其实对这个LCS得动态规划解法已叙述殆尽,不过,为了成书得某种必要性,下面,我试着再多加详细阐述这个问题。
kMeUn。
第三节、动态规划算法解LCS问题
3、1、最长公共子序列得结构
最长公共子序列得结构有如下表示:
设序列X=
w7Dqp。
1.若xm=yn,则zk=xm=yn且Zk-1就是Xm-1与Yn-1得最长公共子序列;
2.若xm≠yn且zk≠xm,则Z就是Xm-1与Y得最长公共子序列;
3.若xm≠yn且zk≠yn ,则Z就是X与Yn-1得最长公共子序列。
其中Xm-1=
ytRAP。
3、2、子问题得递归结构
由最长公共子序列问题得最优子结构性质可知,要找出X=
当xm=yn时,找出Xm-1与Yn-1得最长公共子序列,然后在其尾部加上xm(=yn)即可得X与Y得一个最长公共子序列。
当xm≠yn时,必须解两个子问题,即找出Xm-1与Y得一个最长公共子序列及X与Yn-1得一个最长公共子序列。
这两个公共子序列中较长者即为X与Y得一个最长公共子序列。
uBXQB。
由此递归结构容易瞧到最长公共子序列问题具有子问题重叠性质。
例如,在计算X与Y得最长公共子序列时,可能要计算出X与Yn-1及Xm-1与Y得最长公共子序列。
而这两个子问题都包含一个公共子问题,即计算Xm-1与Yn-1得最长公共子序列。
GpagY。
与矩阵连乘积最优计算次序问题类似,我们来建立子问题得最优值得递归关系。
用c[i,j]记录序列Xi与Yj得最长公共子序列得长度。
其中Xi=
当i=0或j=0时,空序列就是Xi与Yj得最长公共子序列,故c[i,j]=0。
其她情况下,由定理可建立递归关系如下:
wvu1K。
3、3、计算最优值
直接利用上节节末得递归式,我们将很容易就能写出一个计算c[i,j]得递归算法,但其计算时间就是随输入长度指数增长得。
由于在所考虑得子问题空间中,总共只有θ(m*n)个不同得子问题,因此,用动态规划算法自底向上地计算最优值能提高算法得效率。
HbbQk。
计算最长公共子序列长度得动态规划算法LCS_LENGTH(X,Y)以序列X=
输出两个数组c[0、、m,0、、n]与b[1、、m,1、、n]。
其中c[i,j]存储Xi与Yj得最长公共子序列得长度,b[i,j]记录指示c[i,j]得值就是由哪一个子问题得解达到得,这在构造最长公共子序列时要用到。
最后,X与Y得最长公共子序列得长度记录于c[m,n]中。
mwrv1。
1.Procedure LCS_LENGTH(X,Y);
2.begin
3. m:
=length[X];
4. n:
=length[Y];
5. for i:
=1 to m do c[i,0]:
=0;
6. for j:
=1 to n do c[0,j]:
=0;
7. for i:
=1 to m do
8. for j:
=1 to n do
9. if x[i]=y[j] then
10. begin
11. c[i,j]:
=c[i-1,j-1]+1;
12. b[i,j]:
="↖";
13. end
14. else if c[i-1,j]≥c[i,j-1] then
15. begin
16. c[i,j]:
=c[i-1,j];
17. b[i,j]:
="↑";
18. end
19. else
20. begin
21. c[i,j]:
=c[i,j-1];
22. b[i,j]:
="←"
23. end;
24. return(c,b);
25.end;
由算法LCS_LENGTH计算得到得数组b可用于快速构造序列X=
首先从b[m,n]开始,沿着其中得箭头所指得方向在数组b中搜索。
eDqOF。
∙当b[i,j]中遇到"↖"时(意味着xi=yi就是LCS得一个元素),表示Xi与Yj得最长公共子序列就是由Xi-1与Yj-1得最长公共子序列在尾部加上xi得到得子序列;iFQ4k。
∙当b[i,j]中遇到"↑"时,表示Xi与Yj得最长公共子序列与Xi-1与Yj得最长公共子序列相同;
∙当b[i,j]中遇到"←"时,表示Xi与Yj得最长公共子序列与Xi与Yj-1得最长公共子序列相同。
这种方法就是按照反序来找LCS得每一个元素得。
由于每个数组单元得计算耗费Ο
(1)时间,算法LCS_LENGTH耗时Ο(mn)。
nsXLv。
3、4、构造最长公共子序列
下面得算法LCS(b,X,i,j)实现根据b得内容打印出Xi与Yj得最长公共子序列。
通过算法得调用LCS(b,X,length[X],length[Y]),便可打印出序列X与Y得最长公共子序列。
BWA3s。
1.Procedure LCS(b,X,i,j);
2.begin
3. if i=0 or j=0 then return;
4. if b[i,j]="↖" then
5. begin
6. LCS(b,X,i-1,j-1);
7. print(x[i]); {打印x[i]}
8. end
9. else if b[i,j]="↑" then LCS(b,X,i-1,j)
10. else LCS(b,X,i,j-1);
11.end;
在算法LCS中,每一次得递归调用使i或j减1,因此算法得计算时间为O(m+n)。
例如,设所给得两个序列为X=与Y=。
由算法LCS_LENGTH与LCS计算出得结果如下图所示:
vNHc7。
我来说明下此图(参考算法导论)。
在序列X={A,B,C,B,D,A,B}与Y={B,D,C,A,B,A}上,由LCS_LENGTH计算出得表c与b。
第i行与第j列中得方块包含了c[i,j]得值以及指向b[i,j]得箭头。
在c[7,6]得项4,表得右下角为X与Y得一个LCS得长度。
对于i,j>0,项c[i,j]仅依赖于就是否有xi=yi,及项c[i-1,j]与c[i,j-1]得值,这几个项都在c[i,j]之前计算。
为了重构一个LCS得元素,从右下角开始跟踪b[i,j]得箭头即可,这条路径标示为阴影,这条路径上得每一个“↖”对应于一个使xi=yi为一个LCS得成员得项(高亮标示)。
grBVS。
所以根据上述图所示得结果,程序将最终输出:
“BCBA”。
3、5、算法得改进
对于一个具体问题,按照一般得算法设计策略设计出得算法,往往在算法得时间与空间需求上还可以改进。
这种改进,通常就是利用具体问题得一些特殊性。
mKWHm。
例如,在算法LCS_LENGTH与LCS中,可进一步将数组b省去。
事实上,数组元素c[i,j]得值仅由c[i-1,j-1],c[i-1,j]与c[i,j-1]三个值之一确定,而数组元素b[i,j]也只就是用来指示c[i,j]究竟由哪个值确定。
因此,在算法LCS中,我们可以不借助于数组b而借助于数组c本身临时判断c[i,j]得值就是由c[i-1,j-1],c[i-1,j]与c[i,j-1]中哪一个数值元素所确定,代价就是Ο
(1)时间。
既然b对于算法LCS不就是必要得,那么算法LCS_LENGTH便不必保存它。
这一来,可节省θ(mn)得空间,而LCS_LENGTH与LCS所需要得时间分别仍然就是Ο(mn)与Ο(m+n)。
不过,由于数组c仍需要Ο(mn)得空间,因此这里所作得改进,只就是在空间复杂性得常数因子上得改进。
HJXnB。
另外,如果只需要计算最长公共子序列得长度,则算法得空间需求还可大大减少。
事实上,在计算c[i,j]时,只用到数组c得第i行与第i-1行。
因此,只要用2行得数组空间就可以计算出最长公共子序列得长度。
更进一步得分析还可将空间需求减至min(m,n)。
hwGUN。
第四节、编码实现LCS问题
动态规划得一个计算最长公共子序列得方法如下,以两个序列 X、Y 为例子:
设有二维数组 f[i][j] 表示 X 得 i 位与 Y 得 j 位之前得最长公共子序列得长度,则有:
CebGR。
f[1][1]= same(1,1)
f[i][j]= max{f[i −1][j −1]+same(i,j), f[i −1][j] ,f[i][j −1]}u8V2b。
其中,same(a,b)当 X 得第 a 位与 Y 得第 b 位完全相同时为“1”,否则为“0”。
此时,f[i][j]中最大得数便就是 X 与 Y 得最长公共子序列得长度,依据该数组回溯,便可找出最长公共子序列。
VJTuq。
该算法得空间、时间复杂度均为O(n2),经过优化后,空间复杂度可为O(n),时间复杂度为O(nlogn)。
HFzrd。
以下就是此算法得java代码:
1.
2.import java、util、Random;
3.
4.public class LCS{
5. public static void main(String[] args){
6.
7. //设置字符串长度
8. int substringLength1 = 20;
9. int substringLength2 = 20; //具体大小可自行设置
10.
11. // 随机生成字符串
12. String x = GetRandomStrings(substringLength1);
13. String y = GetRandomStrings(substringLength2);
14.
15. Long startTime = System、nanoTime();
16. // 构造二维数组记录子问题x[i]与y[i]得LCS得长度
17. int[][] opt = new int[substringLength1 + 1][substringLength2 + 1]; tThyN。
18.
19. // 动态规划计算所有子问题
20. for (int i = substringLength1 - 1; i >= 0; i--){
21. for (int j = substringLength2 - 1; j >= 0; j--){
22. if (x、charAt(i) == y、charAt(j))
23. opt[i][j] = opt[i + 1][j + 1] + 1; //参考上文我给得公式。
nIw8K。
24. else
25. opt[i][j] = Math、max(opt[i + 1][j], opt[i][j + 1]); //参考上文我给得公式。
1hkwT。
26. }
27. }
28.
29. ------------------------------------------------------------------------------------- xXnYQ。
30.
31. 理解上段,参考上文我给得公式:
32.
33. 根据上述结论,可得到以下公式,
34.
35. 如果我们记字符串Xi与Yj得LCS得长度为c[i,j],我们可以递归地求c[i,j]:
36.
37. / 0 if i<0 or j<0 hCv7p。
38. c[i,j]= c[i-1,j-1]+1 if i,j>=0 and xi=xj 5gTBT。
39. / max(c[i,j-1],c[i-1,j] if i,j>=0 and xi≠xj Cehsp。
40.
41. ------------------------------------------------------------------------------------- hma9G。
42.
43. System、out、println("substring1:
"+x);
44. System、out、println("substring2:
"+y);
45. System、out、print("LCS:
");
46.
47. int i = 0, j = 0;
48. while (i < substringLength1 && j < substringLength2){ QdMTd。
49. if (x、charAt(i) == y、charAt(j)){
50. System、out、print(x、charAt(i));
51. i++;
52. j++;
53. } else if (opt[i + 1][j] >= opt[i][j + 1])
54. i++;
55. else
56. j++;
57. }
58. Long endTime = System、nanoTime();
59. System、out、println(" Totle time is " + (endTime - startTime) + " ns"); me5DM。
60. }
61.
62. //取得定长随机字符串
63. public static String GetRandomStrings(int length){
64. StringBuffer buffer = new StringBuffer("abcdefghijklmnopqrstuvwxyz"); yKfFD。
65. StringBuffer sb = new StringBuffer();
66. Random r = new Random();
67. int range = buffer、length();
68. for (int i = 0; i < length; i++){
69. sb、append(buffer、charAt(r、nextInt(range)));
70. }
71. return sb、toString();
72. }
73.}
第五节、改进得算法
下面咱们来了解一种不同于动态规划法得一种新得求解最长公共子序列问题得方法,该算法主要就是把求解公共字符串问题转化为求解矩阵L(p,m)得问题,在利用定理求解矩阵得元素过程中
(1)while(i (2)while(L(k,i)=k),L(k,i+1)=L(k,i+2)=…L(k,m)=k;zZlRJ。 求出每列元素,一直到发现第p+1行都为null时退出循环,得出矩阵L(k,m)后,B[L(1,m-p+1)]B[L(2,m-p+2)]…B[L(p,m)]即为A与B得LCS,其中p为LCS得长度。 Jsxve。 6、1主要定义及定理 ∙定义1子序列(Subsequence): 给定字符串A=A[1]A[2]…A[m],(A[i]就是A得第i个字母,A[i]∈字符集Σ,l<=i ∙定义2公共子序列(monSubsequence): 给定字符串A、B、C,C称为A与B得公共子序列就是指C既就是A得子序列,又就是B得子序列。 OS1iB。 ∙定义3最长公共子序列(LongestmonSubsequence简称LCS): 给定字符串A、B、C,C称为A与B得最长公共子序列就是指C就是A与B得公共子序列,且对于A与B得任意公共子序列D,都有D<=C。 给定字符串A与B,A=m,B=n,不妨设m<=n,LCS问题就就是要求出A与B得LCS。 ddzzS。 ∙定义4给定字符串A=A[1]A[2]…A[m]与字符串B=B[1]B[2]…[n],A(1: i)表示A得连续子序列A[1]A[2]…A[i],同样B(1: j)表示B得连续子序列B[1]B[2]…[j]。 Li(k)表示所有与字符串A(1: i)有长度为k得LCS得字符串B(l: j)中j得最小值。 用公式表示就就是Li(k)=Minj(LCS(A(1: i),B(l: j))=k)
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- lcs 算法 详解