当前位置:   article > 正文

使用动态规划解决最长公共子序列问题_本关任务:编写用动态规划解决最长公共子序列问题。

本关任务:编写用动态规划解决最长公共子序列问题。

一、定义:

给定两个序列X和Y,如果Z既是X的子序列也是Y的子序列,那么我们称Z是X和Y的公共子序列。例如:X={a,b,c,e,d,g,f},Y={b,e,f,g},那么Z={b}、Z={b,e}、Z={b,e,f}都是X和Y的公共子序列,其中Z={b,e,f}是X和Y的最长公共子序列。求解X和Y的最长公共子序列就是LCS问题。最长公共子序列不唯一,但是其长度是唯一的。

二、定理:

令X={x1,x2,...,xm}和Y={y1,y2,....yn}为两个序列,Z={z1,z2,...zk}为这两个序列的最长公共子序列,则:

①如果xm=yn,那么zk = xm = yn,且Z(k-1)是X(m-1)和Y(n-1)的一个最长公共子序列;

②如果xm != yn,zk != xm,那么Z(k-1)是X(m-1)和Yn的一个最长公共子序列;

③如果xm != yn,zk != yn,那么Z(k-1)是Xm和Y(n-1)的一个最长公共子序列;

这样我们在求解X和Y的LCS时,可以分解为求解一个或两个这样的子问题。当xm = yn,我们需要求解X(m-1)和Y(n-1)的LCS;当xm != yn时,我们需要求解X(m-1)和Yn的一个LCS,以及求解Xm和Y(n-1)的一个LCS,两个较长者既为X和Y的LCS。

三、求解步骤:

如果用暴力搜索法求解LCS问题,就要穷举X的所有子序列,对于每个子序列都要检查它是否也是Y的子序列,从而找到最长子序列。X的每个子序列对应下表集合{1,2,3,....,m},所以X有2^m个子序列,因此暴力搜索法的运行时间为指数阶,对较长的序列是不适用的,但是根据以上定理,LCS问题具有最优子结构性质。

我们定义一个二维数组c[i][j]用来记录Xi和Yj的LCS的长度。如果i=0,或者j=0,既一个序列长度为0,那么LCS的长度为0。根据最优子结构性质,可得如下公式

  

上述公式适用于字符串下标为0时存储的是字符串的长度,然而我们常见的字符串是'\0'为结尾的,下标为0的内容存储的并不是字符串的长度,因此我们把公式稍作修改以适应解决我们常见的字符串


根据以上公式我们,可以用动态规划方法自顶向上地计算c[i][j]的值。

以下为计算c[i][j]的值,并给出测试程序:

  1. #include <iostream>
  2. #define MAX_LEN 100
  3. using namespace std;
  4. /*给定两个字符串X和Y,求出Xi和Yj的最长公共子序列的长度,记录在c[i][j]数组中*/
  5. void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN]);
  6. int main()
  7. {
  8. char X[] = "abaddc";
  9. char Y[] = "badcaef";
  10. int m = strlen(X);
  11. int n = strlen(Y);
  12. int c[MAX_LEN][MAX_LEN];
  13. get_lcslength(X, m, Y, n, c);
  14. for (int i = 0; i < m;i++)
  15. {
  16. for (int j = 0; j < n;j++)
  17. {
  18. cout << c[i][j] <<" ";
  19. }
  20. cout << endl;
  21. }
  22. return 0;
  23. }
  24. <pre name="code" class="cpp">void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN])
  25. {
  26. int i = 0, j = 0;
  27. /*初始化第0行,如果X[0]=Y[i],则说明LCS的长度为1*/
  28. while (i < Ylen && X[0] != Y[i])
  29. {
  30. c[0][i] = 0;
  31. i++;
  32. }
  33. for (; i < Ylen; i++)
  34. c[0][i] = 1;
  35. /*同理,初始化第0列*/
  36. while (j < Xlen && Y[0] != X[j])
  37. {
  38. c[j][0] = 0;
  39. j++;
  40. }
  41. for (; j < Xlen; j++)
  42. c[j][0] = 1;
  43. for (i = 1; i < Xlen;i++)
  44. {
  45. for (j = 1; j < Ylen;j++)
  46. {
  47. if (X[i] == Y[j])
  48. c[i][j] = c[i - 1][j - 1] + 1;
  49. else if (c[i - 1][j] >= c[i][j - 1])
  50. c[i][j] = c[i - 1][j];
  51. else
  52. c[i][j] = c[i][j - 1];
  53. }
  54. }
  55. }

 
 程序的运行结果如下: 

我们一张表格来解释上面的程序:

第i行和第j列记录了c[i][j]的值,对于第0行第0列X[0] != Y[0],故c[0][0]=0,对于第0行第1列,X[0]=Y[1],因此c[0][1]=1,那么第0行从第1列开始及以后的列都为1;同理可求出第0列的所有c[i][0]的值。对于i>0,j>0的,标项c[i][j]的值取决于X[i]是否等于Y[j]及c[i-1][j-1]、c[i-1][j]、c[i][j-1]的值,这些值都会在计算c[i][j]的值之前计算出来。c[5][6]记录了X和Y的LCS的长度。

当我们求出二维数组c的值之后,我们可以根据这个二维数组求解LCS,通过上述分析我们可以了解到:c[i][j]的值只取决于c[i-1][j-1]、c[i-1][j]、c[i][j-1]这三项,给出c[i][j]的值我们可以在O(1)时间内判断出计算c[i][j]的值使用了这三项中的哪一项,我们配合下面的图说明一下算法的实现原理:

图中的箭头表示当计算c[i][j]时所使用的上一个元素是谁,首先c[i][j]与c[i][j-1]作比较,如果相等则表明计算c[i][j]时使用的是c[i][j-1],此时令j=j-1;否则,c[i][j]与c[i-1][j]作比较,如果相等则表明计算c[i][j]时使用的是c[i-1][j],此时令i=i-1;否则表明计算c[i][j]时使用的是c[i-1][j-1],那么这时X[i] == Y[j],将这个相等的字符记录到str数组中,并令j=j-1,i=i-1。进行循环比较直到i、j有一个出现0时为止。代码如下:

  1. #include <iostream>
  2. #define MAX_LEN 100
  3. using namespace std;
  4. /*给定两个字符串X和Y,求出Xi和Yj的最长公共子序列的长度,记录在c[i][j]数组中*/
  5. void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN]);
  6. /*给定两个字符串X和Y,将最长公共子序列保存在str数组中*/
  7. char *lcs(const char X[], const char Y[], char str[]);
  8. int main()
  9. {
  10. char X[] = "abaddc";
  11. char Y[] = "badcaef";
  12. char str[MAX_LEN];
  13. lcs(X, Y, str);
  14. cout << "最长公共子序列为:" << str << " 长度为:" << strlen(str)<<endl;
  15. lcs("acbecda", "abcdaef", str);
  16. cout << "最长公共子序列为:" << str << " 长度为:" << strlen(str) << endl;
  17. lcs("gcbecda", "hbcdaef", str);
  18. cout << "最长公共子序列为:" << str << " 长度为:" << strlen(str) << endl;
  19. lcs("ghijklmna", "hbcdaef", str);
  20. cout << "最长公共子序列为:" << str << " 长度为:" << strlen(str) << endl;
  21. lcs("gijklmn", "hbcdaef", str);
  22. cout << "最长公共子序列为:" << str << " 长度为:" << strlen(str) << endl;
  23. return 0;
  24. }
  25. void get_lcslength(const char X[],int Xlen, const char Y[],int Ylen, int c[][MAX_LEN])
  26. {
  27. int i = 0, j = 0;
  28. /*初始化第0行,如果X[0]=Y[i],则说明LCS的长度为1*/
  29. while (i < Ylen && X[0] != Y[i])
  30. {
  31. c[0][i] = 0;
  32. i++;
  33. }
  34. for (; i < Ylen; i++)
  35. c[0][i] = 1;
  36. /*同理,初始化第0列*/
  37. while (j < Xlen && Y[0] != X[j])
  38. {
  39. c[j][0] = 0;
  40. j++;
  41. }
  42. for (; j < Xlen; j++)
  43. c[j][0] = 1;
  44. for (i = 1; i < Xlen;i++)
  45. {
  46. for (j = 1; j < Ylen;j++)
  47. {
  48. if (X[i] == Y[j])
  49. c[i][j] = c[i - 1][j - 1] + 1;
  50. else if (c[i - 1][j] >= c[i][j - 1])
  51. c[i][j] = c[i - 1][j];
  52. else
  53. c[i][j] = c[i][j - 1];
  54. }
  55. }
  56. }
  57. char *lcs(const char X[], const char Y[], char str[])
  58. {
  59. int Xlen = strlen(X);
  60. int Ylen = strlen(Y);
  61. int c[MAX_LEN][MAX_LEN];
  62. get_lcslength(X, Xlen, Y, Ylen, c);
  63. int i = Xlen - 1;
  64. int j = Ylen - 1;
  65. str[c[i][j]] = '\0';//此时的c[i][j]记录着最长公共子序列的长度,让str以'\0'结尾
  66. while (i > 0 && j > 0)
  67. {
  68. if (c[i][j] == c[i][j - 1])
  69. j = j - 1;
  70. else if (c[i][j] == c[i - 1][j])
  71. i = i - 1;
  72. else
  73. {
  74. str[c[i][j] - 1] = X[i];
  75. i = i - 1;
  76. j = j - 1;
  77. }
  78. }
  79. /*判断第0行或者第0列的值,如果为1则表明X[i] == Y[j]*/
  80. if (c[i][j] == 1)
  81. {
  82. if (i == 0)
  83. {
  84. str[0] = X[0];
  85. }
  86. if (j == 0)
  87. {
  88. str[0] = Y[0];
  89. }
  90. }
  91. return str;
  92. }

程序的运行结果如下所示:





声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/201942
推荐阅读
相关标签
  

闽ICP备14008679号