数据布局之字符串的最长公共子序列题目详解与示例(C,C++) ...

打印 上一主题 下一主题

主题 553|帖子 553|积分 1659



字符串的最长公共子序列(Longest Common Subsequence, LCS)是计算机科学中的一个经典题目,属于动态规划(Dynamic Programming, DP)的范畴。在本博客中,我们将详细讲解最长公共子序列的概念,并给出 C 和 C++ 语言的实现示例。
1、最长公共子序列定义

最长公共子序列题目可以如许描述:给定两个字符串序列 X 和 Y,求出它们的最长公共子序列 Z。这里的子序列指的是原序列中元素次序的连续序列,但不要求元素在原序列中连续。例如,ABCD 和 ACDF 的一个最长公共子序列是 ACD。
2、动态规划解法

动态规划是解决此类题目的一种高效方法,其根本头脑是将大题目分解为小题目,先求解小题目,然后利用这些小题目的解来构造原题目的解。对于最长公共子序列题目,我们可以用一个二维数组 dp 来存储两个字符串的前缀的公共子序列长度。
3、状态转移方程

假设我们有两个字符串 X[1…n] 和 Y[1…m],动态规划表 dp[][] 的第 i 行第 j 列的元素表现 X[1…i] 和 Y[1…j] 的公共子序列的长度。状态转移方程如下:


  • 当 X = Y[j] 时,dp[j] = dp[i-1][j-1] + 1;
  • 当 X != Y[j] 时,dp[j] = max(dp[i-1][j], dp[j-1])。
这里 dp[i-1][j-1] 表现 X[1…i-1] 和 Y[1…j-1] 的公共子序列长度,dp[i-1][j] 表现 X[1…i] 和 Y[1…j-1] 的公共子序列长度,dp[j-1] 表现 X[1…i-1] 和 Y[1…j] 的公共子序列长度。
初始化

初始化 dp[0][j] = 0 (对于所有 0 <= j < m)和 dp[0] = 0 (对于所有 0 <= i < n),因为任何序列与一个空序列都有一个公共子序列长度为0。
构建最长公共子序列

根据动态规划表,我们可以从 dp[n][m] 开始,逆向追踪得到最长公共子序列 Z[1…n+m]。当我们得到 dp[j] 时,有两种环境:
如果 X = Y[j],则 Z[k] = X 而且 k++,然后我们递归地求 dp[i-1][j-1];
如果 X != Y[j],则我们分别递归地求 dp[i-1][j] 和 dp[j-1],取较大的一个。
4、C 和 C++ 实现示例

下面是使用 C 和 C++ 语言实现最长公共子序列的代码示例:
C 语言实现

  1. #include <stdio.h>
  2. #include <string.h>
  3. void printLCS(char X[], char Y[], int dp[][100]) {
  4.     int m = strlen(X);
  5.     int n = strlen(Y);
  6.     int i, j;
  7.     for (i = m, j = n; i > 0 && j > 0; i--, j--) {
  8.         if (X[i - 1] == Y[j - 1]) {
  9.             printf("%c", X[i - 1]);
  10.             X++;
  11.             Y++;
  12.         } else if (dp[i - 1][j] > dp[i][j - 1]) {
  13.             i--;
  14.         } else {
  15.             j--;
  16.         }
  17.     }
  18. }
  19. int LCSLength(char X[], char Y[], int m, int n) {
  20.     int dp[100][100];
  21.     int i, j;
  22.     // 初始化动态规划表
  23.     for (i = 0; i <= m; i++) {
  24.         dp[i][0] = 0;
  25.     }
  26.     for (j = 0; j <= n; j++) {
  27.         dp[0][j] = 0;
  28.     }
  29.     // 动态规划填表
  30.     for (i = 1; i <= m; i++) {
  31.         for (j = 1; j <= n; j++) {
  32.             if (X[i - 1] == Y[j - 1]) {
  33.                 dp[i][j] = dp[i - 1][j - 1] + 1;
  34.             } else {
  35.                 dp[i][j] = (dp[i - 1][j] > dp[i][j - 1]) ? dp[i - 1][j] : dp[i][j - 1];
  36.             }
  37.         }
  38.     }
  39.     // 打印最长公共子序列
  40.     printLCS(X, Y, dp);
  41.     return dp[m][n];
  42. }
  43. int main() {
  44.     char X[] = "AGGTAB";
  45.     char Y[] = "GXTXAYB";
  46.     int m = strlen(X);
  47.     int n = strlen(Y);
  48.     printf("最长公共子序列的长度为 %d\n", LCSLength(X, Y, m, n));
  49.     return 0;
  50. }
复制代码
C++ 语言实现

  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. std::string LCS(const std::string& X, const std::string& Y) {
  5.     int m = X.length();
  6.     int n = Y.length();
  7.     std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
  8.     std::string lcs;
  9.     // 动态规划填表
  10.     for (int i = 1; i <= m; i++) {
  11.         for (int j = 1; j <= n; j++) {
  12.             if (X[i - 1] == Y[j - 1]) {
  13.                 dp[i][j] = dp[i - 1][j - 1] + 1;
  14.             } else {
  15.                 dp[i][j] = (dp[i - 1][j] > dp[i][j - 1]) ? dp[i - 1][j] : dp[i][j - 1];
  16.             }
  17.         }
  18.     }
  19.     // 构建最长公共子序列
  20.     int i = m, j = n;
  21.     while (i > 0 && j > 0) {
  22.         if (X[i - 1] == Y[j - 1]) {
  23.             lcs += X[i - 1];
  24.             i--;
  25.             j--;
  26.         } else if (dp[i - 1][j] > dp[i][j - 1]) {
  27.             i--;
  28.         } else {
  29.             j--;
  30.         }
  31.     }
  32.     // 输出结果
  33.     std::reverse(lcs.begin(), lcs.end());
  34.     std::cout << "最长公共子序列是: " << lcs << std::endl;
  35.     return lcs;
  36. }
  37. int main() {
  38.     std::string X = "AGGTAB";
  39.     std::string Y = "GXTXAYB";
  40.     std::string lcs = LCS(X, Y);
  41.     return 0;
  42. }
复制代码
在这两个示例中,我们起首初始化了一个动态规划表 dp,然后使用状态转移方程添补它。最后,我们通过回溯动态规划表来构建并打印最长公共子序列。在 C++ 示例中,我们使用了 std::vector 来存储动态规划表,这使得代码更加清晰和易于管理。
5、总结

本文详细先容了最长公共子序列(LCS)题目的原理,并通过C/C++语言给出了详细的实现。LCS题目是一个经典的动态规划题目,通过构建状态转移方程,我们可以高效地求解两个字符串的最长公共子序列。在实际应用中,LCS题目可以扩展到多个字符串的环境,也可以结合其他算法优化求解过程,如后缀数组、后缀树等。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

惊雷无声

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表