目录
最长公共子序列的定义:
子序列:若给定序列X={x1,x2,…,xm},则另一序列Z={z1,z2,…,zk},是X的子序列是指存在一个严格递增下标序列{i1,i2,…,ik}使得对于所有j=1,2,…,k有:zj=xij.
公共子序列:给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列.
最长公共子序列:给定2个序列X={x1,x2,…,xm}和Y={y1,y2,…,yn},找出X和Y的最长公共子序列.
如:序列ABCDEF和ADFGH的最长公共子序列为ADF
⭐注意: 最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,简称LCS)的区别为是最长公共子串的串是一个连续的部分,而最长公共子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;通俗的说就是子串中字符的位置必须是连续的而子序列则可以不必连续。
动态规划法分析:
例如给定两个子序列:X=ABCBDAB , Y=BDCABC
算法思路:
-
初始化: 初始化一个二维数组
L
,其中L[i, j]
表示X
的前i
个字符和Y
的前j
个字符的最长公共子序列长度。L
的第一行和第一列全部初始化为 0。 -
递推: 对于
i
和j
大于 0 的情况,根据公式判断X
的第i
个字符和Y
的第j
个字符是否相等。- 如果相等,则
L[i, j]
=L[i-1, j-1]
+ 1。 - 如果不相等,则
L[i, j]
等于L[i, j-1]
和L[i-1, j]
中的最大值。
- 如果相等,则
-
结果:
L
数组的最后一个元素L[m, n]
(其中m
和n
分别是X
和Y
的长度)即为X
和Y
的最长公共子序列长度。
解读图片:
首先:蓝色的标注的是 由 Xi=Yi 得来的,红色的线即可 看出来 公共子序列的字母(图中是BDAB)
伪代码:
观众老爷们,这个伪代码超级简单,可以记下来哦,期末一定会考的!!!
function longestCommonSubsequence(X[1..m], Y[1..n]):
// 初始化二维数组L
for i from 0 to m:
L[i][0] = 0
for j from 0 to n:
L[0][j] = 0
// 计算LCS长度
for i from 1 to m:
for j from 1 to n:
if X[i] == Y[j]:
L[i][j] = L[i-1][j-1] + 1
else:
L[i][j] = max(L[i-1][j], L[i][j-1])
return L[m][n]
代码实现:
#include <iostream>
#include <vector>
#include <algorithm>
int longestCommonSubsequence(std::string X, std::string Y) {
int m = X.length();
int n = Y.length();
// 初始化二维数组L
std::vector<std::vector<int>> L(m+1, std::vector<int>(n+1, 0));
// 计算LCS长度
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (X[i-1] == Y[j-1]) {
L[i][j] = L[i-1][j-1] + 1;
} else {
L[i][j] = std::max(L[i-1][j], L[i][j-1]);
}
}
}
return L[m][n];
}
int main() {
std::string X = "ABCBDAB";
std::string Y = "BDCAB";
std::cout << "Length of Longest Common Subsequence: " << longestCommonSubsequence(X, Y) << std::endl;
return 0;
}
结果:
小结:
这个算法的好处是它能够有效地找到给定两个序列的最长公共子序列,其时间复杂度为O(mn),其中m和n分别是两个输入序列的长度。动态规划的思路使得算法在解决这个问题时表现出很高的效率。然而,对于非常长的序列,这个算法可能会占用大量的内存,因为需要创建一个大小为(m+1)x(n+1)的二维数组来存储中间计算结果。另外,虽然动态规划算法能够给出最优的解,但是通常需要较多的额外空间和计算时间来实现这一点。在实际应用中,可能还需要考虑是否有更加高效的近似算法或者优化策略来解决实际问题。我是一个小菜鸡,欢迎各路大神批评指正!!