0011算法笔记动态规划最长公共子序列问题LCS.docx
- 文档编号:8895440
- 上传时间:2023-02-02
- 格式:DOCX
- 页数:20
- 大小:199.71KB
0011算法笔记动态规划最长公共子序列问题LCS.docx
《0011算法笔记动态规划最长公共子序列问题LCS.docx》由会员分享,可在线阅读,更多相关《0011算法笔记动态规划最长公共子序列问题LCS.docx(20页珍藏版)》请在冰豆网上搜索。
0011算法笔记动态规划最长公共子序列问题LCS
问题描述:
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。
确切地说,若给定序列X= {x1,x2,…,xm},则另一序列Z={z1,z2,…,zk}是X的子序列是指存在一个严格递增的下标序列{i1,i2,…,ik},使得对于所有j=1,2,…,k有Xij=Zj。
例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列,相应的递增下标序列为{2,3,5,7}。
给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
例如,若X= {A,B,C,B,D,A,B}和Y= {B,D,C,A,B,A},则序列{B,C,A}是X和Y的一个公共子序列,序列{B,C,B,A}也是X和Y的一个公共子序列。
而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。
给定两个序列X= {x1,x2,…,xm}和Y= {y1,y2,…,yn},要求找出X和Y的一个最长公共子序列。
问题解析:
设X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A}。
求X,Y的最长公共子序列最容易想到的方法是穷举法。
对X的多有子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列。
由集合的性质知,元素为m的集合共有2^m个不同子序列,因此,穷举法需要指数级别的运算时间。
进一步分解问题特性,最长公共子序列问题实际上具有最优子结构性质。
设序列X={x1,x2,……xm}和Y={y1,y2,……yn}的最长公共子序列为Z={z1,z2,……zk}。
则有:
(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={x1,x2……xm-1},Yn-1={y1,y2……yn-1},Zk-1={z1,z2……zk-1}。
递推关系:
用c[i][j]记录序列Xi和Yj的最长公共子序列的长度。
其中,Xi={x1,x2……xi},Yj={y1,y2……yj}。
当i=0或j=0时,空序列是xi和yj的最长公共子序列。
此时,c[i][j]=0;当i,j>0,xi=yj时,c[i][j]=c[i-1][j-1]+1;当i,j>0,xi!
=yj时,
c[i][j]=max{c[i][j-1],c[i-1][j]},由此建立递推关系如下:
构造最优解:
由以上分析可知,要找出X={x1,x2,……xm}和Y={y1,y2,……yn}的最长公共子序列,可以按一下方式递归进行:
当xm=yn时,找出xm-1和yn-1的最长公共子序列,然后在尾部加上xm(=yn)即可得X和Y的最长公共子序列。
当Xm!
=Yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。
这两个公共子序列中较长者为X和Y的最长公共子序列。
设数组b[i][j]记录c[i][j]的值由哪一个子问题的解得到的,从b[m][n]开始,依其值在数组b中搜索,当b[i][j]=1时,表示Xi和Yj的最长公共子序列是由Xi-1和Yj-1的最长公共子序列在尾部加上xi所得到的子序列。
当b[i][j]=2时,表示Xi和Yj的最长公共子序列与Xi-1和Yj-1的最长公共子序列相同。
当b[i][j]=3时,表示Xi和Yj的最长公共子序列与Xi和Yj-1的最长公共子序列相同。
代码如下:
[cpp] viewplain copy
1.//3d3-1 最长公共子序列问题
2.#include "stdafx.h"
3.#include
4.using namespace std;
5.
6.const int M = 7;
7.const int N = 6;
8.
9.void output(char *s,int n);
10.void LCSLength(int m,int n,char *x,char *y,int **c,int **b);
11.void LCS(int i,int j,char *x,int **b);
12.
13.int main()
14.{
15. //X={A,B,C,B,D,A,B}
16. //Y={B,D,C,A,B,A}
17. char x[] = {' ','A','B','C','B','D','A','B'};
18. char y[] = {' ','B','D','C','A','B','A'};
19.
20. int **c = new int *[M+1];
21. int **b = new int *[M+1];
22. for(int i=0;i<=M;i++)
23. {
24. c[i] = new int[N+1];
25. b[i] = new int[N+1];
26. }
27.
28. cout<<"序列X:
"< 29. output(x,M); 30. cout<<"序列Y: "< 31. output(y,N); 32. 33. LCSLength(M,N,x,y,c,b); 34. 35. cout<<"序列X、Y最长公共子序列长度为: "< 36. cout<<"序列X、Y最长公共子序列为: "< 37. LCS(M,N,x,b); 38. cout< 39.} 40. 41.void output(char *s,int n) 42.{ 43. for(int i=1; i<=n; i++) 44. { 45. cout< 46. } 47. cout< 48.} 49. 50.void LCSLength(int m,int n,char *x,char *y,int **c,int **b) 51.{ 52. int i,j; 53. 54. for(i=1; i<=m; i++) 55. c[i][0] = 0; 56. for(i=1; i<=n; i++) 57. c[0][i] = 0; 58. 59. for(i=1; i<=m; i++) 60. { 61. for(j=1; j<=n; j++) 62. { 63. if(x[i]==y[j]) 64. { 65. c[i][j]=c[i-1][j-1]+1; 66. b[i][j]=1; 67. } 68. else if(c[i-1][j]>=c[i][j-1]) 69. { 70. c[i][j]=c[i-1][j]; 71. b[i][j]=2; 72. } 73. else 74. { 75. c[i][j]=c[i][j-1]; 76. b[i][j]=3; 77. } 78. } 79. } 80.} 81. 82.void LCS(int i,int j,char *x,int **b) 83.{ 84. if(i==0 || j==0) 85. { 86. return; 87. } 88. if(b[i][j]==1) 89. { 90. LCS(i-1,j-1,x,b); 91. cout< 92. } 93. else if(b[i][j]==2) 94. { 95. LCS(i-1,j,x,b); 96. } 97. else 98. { 99. LCS(i,j-1,x,b); 100. } 101.} LCSLength函数在计算最优值时,分别迭代X,Y构造数组b,c。 设数组每个元素单元计算耗费时间O (1),则易得算法LCSLength的时间复杂度为O(mn)。 在算法LCS中,依据数组b的值回溯构造最优解,每一次递归调用使i,或j减小1。 从而算法的计算时间为O(m+n)。 LCS的回溯构造最优解过程如下图所示: 算法的改进: 对于一个具体问题,按照一般的算法设计策略设计出的算法,往往在算法的时间和空间需求上还可以改进。 这种改进,通常是利用具体问题的一些特殊性。 例如,在算法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[i][j]时,只用到数组c的第i行和第i-1行。 因此,只要用2行的数组空间就可以计算出最长公共子序列的长度。 更进一步的分析还可将空间需求减至min(m,n)。 [cpp] viewplain copy 1.//3d3-2 最长公共子序列问题 2.#include "stdafx.h" 3.#include 4.using namespace std; 5. 6.const int M = 7; 7.const int N = 6; 8. 9.void output(char *s,int n); 10.void LCSLength(int m,int n,char *x,char *y,int **c); 11.void LCS(int i,int j,char *x,int **c); 12. 13.int main() 14.{ 15. //X={A,B,C,B,D,A,B} 16. //Y={B,D,C,A,B,A} 17. char x[] = {' ','A','B','C','B','D','A','B'}; 18. char y[] = {' ','B','D','C','A','B','A'}; 19. 20. int **c = new int *[M+1]; 21. for(int i=0;i<=M;i++) 22. { 23. c[i] = new int[N+1]; 24. } 25. 26. cout<<"序列X: "< 27. output(x,M); 28. cout<<"序列Y: "< 29. output(y,N); 30. 31. LCSLength(M,N,x,y,c); 32. 33. cout<<"序列X、Y最长公共子序列长度为: "< 34. cout<<"序列X、Y最长公共子序列为: "< 35. LCS(M,N,x,c); 36. cout< 37.} 38. 39.void output(char *s,int n) 40.{ 41. for(int i=1; i<=n; i++) 42. { 43. cout< 44. } 45. cout< 46.} 47. 48.void LCSLength(int m,int n,char *x,char *y,int **c) 49.{ 50. int i,j; 51. 52. for(i=1; i<=m; i++) 53. c[i][0] = 0; 54. for(i=1; i<=n; i++) 55. c[0][i] = 0; 56. 57. for(i=1; i<=m; i++) 58. { 59. for(j=1; j<=n; j++) 60. { 61. if(x[i]==y[j]) 62. { 63. c[i][j]=c[i-1][j-1]+1; 64. } 65. else if(c[i-1][j]>=c[i][j-1]) 66. { 67. c[i][j]=c[i-1][j]; 68. } 69. else 70. { 71. c[i][j]=c[i][j-1]; 72. } 73. } 74. } 75.} 76. 77.void LCS(int i,int j,char *x,int **c) 78.{ 79. if(i==0 || j==0) 80. { 81. return; 82. } 83. if(c[i][j]==c[i-1][j-1]+1) 84. { 85. LCS(i-1,j-1,x,c); 86. cout< 87. } 88. else if(c[i-1][j]>=c[i][j-1]) 89. { 90. LCS(i-1,j,x,c); 91. } 92. else 93. { 94. LCS(i,j-1,x,c); 95. } 96.} 运行结果如下: 从运行结果中可以看出,算法LCS回溯算法仅仅打印了其中一条最大公共子序列,如果存在多条公共子序列的情况下。 怎么解决? 对b[i][j]二维数组的取值添加一种可能,等于4,这代表了我们说的这种多支情况,那么回溯的时候可以根据这个信息打印更多可能的选择。 你从(7,6)点开始按b[i][j]的值指示的方向回溯,把所有的路径遍历一遍,如果是能达到起点(1,1)的路径,就是LCS了,有多少条打印多少条。 可是,在回溯路径的时候,如果采用一般的全搜索,会进行了很多无用功。 即重复了很多,且会遍历了一些无效路径,因为这些路径最终不会到达终点(1,1),因此加大算法复杂度和时间消耗。 博文《求所有最大公共子序列的算法实现》给出了一种"矩行搜索"的解决办法降低了算法的复杂度。 算法主要是利用两个栈store,print,一个用来储存节点,一个用来打印节点。 栈的实现代码如下(文件Stack.h): [cpp] viewplain copy 1./** 2. 头文件------head file 3. */ 4. 5.template 6.class StackNode{ 7. public: 8. T data; 9. StackNode *next; 10.}; 11. 12.template 13.class Stack{ 14. public: 15. Stack(void): top(NULL){} 16. bool IsEmpty(void) const{ return top==NULL;} 17. void Push(const T data); 18. bool Pop(T *data); 19. bool Peek(T *data) const; 20. StackNode 21. private: 22. StackNode 23.}; 24. 25.template 26.StackNode : GetStackNode(){ 27. return top; 28.} 29. 30.template 31.void Stack : Push(const T data){ 32. StackNode 33. node->data = data; 34. node->next = top; 35. top = node; 36.} 37. 38.template 39.bool Stack : Peek(T *data) const{ 40. if(IsEmpty()) return false; 41. *data = top->data; 42. return true; 43.} 44. 45.template 46.bool Stack : Pop(T *data){ 47. if(IsEmpty()) return false; 48. *data = top->data; 49. StackNode 50. top = top->next; 51. delete(node); 52. return true; 53.} 所有最长公共子序列问题LCS矩阵搜索代码如下: [cpp] viewplain copy 1.//3d3-3 所有最长公共子序列问题LCS 矩阵搜索 2.#include "stdafx.h" 3.#include "stack.h" 4.#include 5.using namespace std; 6. 7.typedef int **Matrix; 8.const int M = 7; 9.const int N = 6; 10. 11.typedef struct _Element 12.{ 13. int lcslen;//当前节点的LCS长度 14. int row;//当前节点的行坐标 15. int col;//当前节点的列坐标 16.}Element; 17. 18.void output(char *s,int n); 19. 20.Element CreateElement(int nlen, int row, int col); 21. 22.Matrix GreateMatrix(int row, int col); 23.void DeleteMatrix(Matrix p, int row); 24. 25.void PrintStack(Stack 26.void SearchE(Matrix pb, int curposx, int curposy, int &eposx, int &eposy, int ntype); 27. 28.void LCSLength(int m,int n,char *x,char *y,Matrix pc,Matrix pb); 29.void LCS(char *x, Matrix pc, Matrix pb, int row, int col);//矩阵搜索回溯 30. 31. 3
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 0011 算法 笔记 动态 规划 最长 公共 序列 问题 LCS