430-动态规划算法-LCS最长公共子序列

灰太狼 2022-09-05 05:15 292阅读 0赞

LCS最长公共子序列

LCS:求两个序列的最长公共子序列的长度
子串(字符必须是连续的)
但是 子序列不一定是连续的

例如:
helloworld
hlweord

这2个子序列的最长公共子序列是: hlweord

我们要求
X : X1,X2…Xn
Y: Y1,Y2…Ym
求两个序列的LCS最长公共子序列长度
我们进行子问题的划分

我们先用分治算法来解决

在这里插入图片描述
在这里插入图片描述
我们看看算法的思维图解:

在这里插入图片描述
继续划分,我们发现会出现子问题的重复求解!
在这里插入图片描述
在这里插入图片描述

子问题重叠了,被重复求解,所以,我们应该使用动态规划算法去解决,动态规划算法对子问题的求解是有记忆性的!

递归版本的动态规划求解

在这里插入图片描述
我们是从后往前推的哦

  1. static int cnt = 0;//用于代码测试
  2. string str1 = "helloworld";
  3. string str2 = "hlweord";
  4. int **dp = nullptr;//记录最长子序列的长度
  5. int **path = nullptr;//记录最长子序列的路径
  6. //递归实现
  7. int LCSA(string X, int n, string Y, int m)
  8. {
  9. if (n < 0 || m < 0)//递归结束的条件
  10. {
  11. return 0;//没有公共子序列,长度是0
  12. }
  13. if (dp[n][m] >= 0)
  14. {
  15. //查表,查子问题的解是否被求过
  16. return dp[n][m];//直接返回问题的解,不重复求解
  17. }
  18. cnt++;//分治算法:628次 动态规划:40次
  19. if (X[n] == Y[m])
  20. {
  21. dp[n][m] = LCS01(X, n - 1, Y, m - 1) + 1;
  22. path[n][m] = 1;//表示n,m 跑向 n-1,m-1 1表示走对角线(左上角)
  23. return dp[n][m];
  24. }
  25. else
  26. {
  27. int len1 = LCS01(X, n, Y, m - 1);
  28. int len2 = LCS01(X, n - 1, Y, m);
  29. if (len1 >= len2)
  30. {
  31. dp[n][m] = len1;
  32. path[n][m] = 2;// n,m跑向n,m-1 2表示表示走左边
  33. }
  34. else
  35. {
  36. dp[n][m] = len2;
  37. path[n][m] = 3;// n,m 跑向n-1,m 3表示走上方
  38. }
  39. return dp[n][m];
  40. }
  41. }

在这里插入图片描述

dp数组的打印结果是:
*代表的是-1
在这里插入图片描述
这个二维数组只是记录了相应的2个子串的LCS最长公共子序列的长度

我们还要记得释放dp这个二维数组的堆内存哦!
path数组打印的结果是:
这个二维数组记录了相应的2个子串的LCS最长公共子序列的字符在这里插入图片描述
在这里插入图片描述
我们根据path的定义
我们从最后一行最后一列的位置(右下角)开始走!!!
1:走对角线(左上)
2:走左边
3:走正上方

在这里插入图片描述
因为对角线是代表:字符相比较是相等的,所以我们看往对角线走的地方,这些地方是相应元素字符相等的地方!!!
对角线的元素组成的字符串是:hlword,这个就是最长公共子序列!!!
在这里插入图片描述

非递归版本的动态规划求解

在这里插入图片描述

我们需要用二维的dp数组来求解。
因为我们需要记录3个变量,两个串和序列的长度

状态:给定的两个序列的LCS的长度
dp[n][m] : n表示第一个串的长度 m表示第二个串的长度,n行m列元素的值,记录的就是这两个串的LCS长度
非递归是从0行0列开始计算的

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <string>
  4. using namespace std;
  5. static int cnt = 0;//用于代码测试
  6. string str1 = "helloworld";
  7. string str2 = "hlweord";
  8. int **dp = nullptr;
  9. int **path = nullptr;//记录最长子序列
  10. //非递归实现
  11. int LCSB(string X, int i, string Y, int j)
  12. {
  13. for (int n = 1; n <= i+1; ++n)//因为n-1不能为负数 所以从1开始 然后n<=i+1,也可以是:m==n==0的时候,直接置为1
  14. {
  15. for (int m = 1; m <= j+1; ++m)//因为m-1不能为负数 所以从1开始 然后m<=j+1
  16. {
  17. if (X[n-1] == Y[m-1])
  18. {
  19. dp[n][m] = 1 + dp[n - 1][m - 1];//n==0 m ==0 走对角线
  20. path[n][m] = 1;
  21. }
  22. else
  23. {
  24. int len1 = dp[n-1][m];//向上面走
  25. int len2 = dp[n][m-1];//向左边走
  26. if (len1 >= len2)
  27. {
  28. dp[n][m] = len1;
  29. path[n][m] = 3;
  30. }
  31. else
  32. {
  33. dp[n][m] = len2;
  34. path[n][m] = 2;
  35. }
  36. }
  37. }
  38. }
  39. return dp[i+1][j+1];
  40. }
  41. void backStrace(string str1, int n, int m)//打印最长公共子串
  42. {
  43. if (n <= 0 || m <= 0)//递归结束条件
  44. {
  45. return;
  46. }
  47. if (path[n][m] == 1)
  48. {
  49. //对应位置的元素是相等的
  50. backStrace(str1, n - 1, m - 1);//向对角线递归
  51. cout << str1[n-1];//回溯的时候再打印
  52. }
  53. else
  54. {
  55. if (path[n][m] == 2)
  56. {
  57. backStrace(str1, n, m - 1);//向左边递归
  58. }
  59. else
  60. {
  61. //path[n][m] = 3
  62. backStrace(str1, n - 1, m);//向上方递归
  63. }
  64. }
  65. }
  66. int main()
  67. {
  68. //dp是一个n行m列的二维数组
  69. int n = str1.size();
  70. int m = str2.size();
  71. dp = new int*[n+1];//n行
  72. for (int i = 0; i < n+1; ++i)
  73. {
  74. dp[i] = new int[m+1];//m列
  75. for (int j = 0; j < m+1; ++j)
  76. {
  77. //dp[i][j] = -1;
  78. dp[i][j] = 0;//初始化为0
  79. }
  80. }
  81. path = new int*[n+1];//n行
  82. for (int i = 0; i < n+1; ++i)
  83. {
  84. path[i] = new int[m+1]();//m列
  85. }
  86. int size = LCS(str1, n-1, str2, m-1);
  87. cout << "LCS length:" << size << endl;
  88. cout << "cnt:" << cnt << endl;
  89. //backStrace(str1, n-1, m-1);
  90. backStrace(str1, n, m);
  91. //for (int i = 0; i < n; ++i) { //行
  92. // for (int j = 0; j < m; ++j) { //列
  93. // cout << path[i][j] << " ";
  94. // }
  95. // cout << endl;
  96. //}
  97. //释放dp数组内存
  98. return 0;
  99. }

在这里插入图片描述

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,292人围观)

还没有评论,来说两句吧...

相关阅读

    相关 动态规划算法-公共序列

    在两个字符串中,有些字符会一样,可以形成的子序列也有可能相等,因此,长度最长的相等子序列便是两者间的最长公共字序列,其长度可以使用动态规划来求。 以s1=\{1,3,4,5,