跳到主要内容

最长公共子序列

提示
  1. 最长公共子序列定义:在多个序列中共同存在的最长子序列,其元素在原序列中不必连续,但必须保持相对顺序。
  2. 动态规划方法:使用动态规划算法构建一个矩阵来找到两个序列的最长公共子序列,通过比较并存储不同序列间的匹配情况。
  3. 应用领域:最长公共子序列算法用于基因组数据压缩和移动电话用户身份验证等领域。

最长公共子序列(LCS)定义为在所有给定序列中都共同存在的最长子序列,前提是子序列的元素不必在原始序列中占据连续的位置。

如果 S1S2 是两个给定的序列,那么 ZS1S2 的公共子序列,如果 ZS1S2 的子序列。此外,Z 必须是 S1S2 索引的严格递增序列

在严格递增序列中,从原始序列中选择的元素的索引必须以 Z 中的升序排列。

如果

S1 = {B, C, D, A, A, C, D}

那么 {A, D, B} 不能是 S1 的子序列,因为元素的顺序不相同(即不是严格递增序列)。

让我们通过一个示例来理解LCS。

如果

S1 = {B, C, D, A, A, C, D}
S2 = {A, C, D, B, A, C}

那么共同子序列包括 {B, C}, {C, D, A, C}, {D, A, C}, {A, A, C}, {A, C}, {C, D}, ...

在这些子序列中,{C, D, A, C} 是最长的公共子序列。我们将使用动态规划来找到这个最长的公共子序列。

在继续之前,如果您还不了解动态规划,请先阅读动态规划

使用动态规划查找LCS

让我们取两个序列:

最长公共子序列第一个序列

最长公共子序列第二个序列

查找最长公共子序列遵循以下步骤。

  1. 创建一个维度为 n+1*m+1 的表格,其中 nm 分别是 XY 的长度。第一行和第一列填充为零。

    初始化表格的最长公共子序列

  2. 使用以下逻辑填充表格的每个单元格。

  3. 如果当前行和当前列对应的字符匹配,则通过将对角元素加一来填充当前单元格。指向对角线单元格。

  4. 否则,从前一列和前一行元素中选择最大值来填充当前单元格。指向具有最大值的单元格。如果它们相等,可以指向任何一个。

    填充值的最长公共子序列

  5. 重复步骤2,直到填满表格。

    填充所有值的最长公共子序列

  6. 最后一行和最后一列中的值是最长公共子序列的长度。

    最长公共子序列长度

  7. 为了找到最长公共子序列,从最后一个元素开始,按照箭头的方向前进。与()符号对应的元素形成最长的公共子序列。

    创建路径的最长公共子序列

因此,最长的公共子序列是 CA

最长公共子序列的结果

在解决LCS问题时,动态规划算法如何比递归算法更高效?

动态规划方法减少了函数调用的次数。它将每个函数调用的结果存储起来,以便将来的调用可以使用,而不需要重复调用。

在上面的动态算法中,从X的元素和Y的元素之间的每次比较中得到的结果都存储在一个表格中,以便在将来的计算中使用。

因此,动态方法的时间复杂度是填充表格所需的时间(即O(mn))。而递归算法的复杂度是2max(m, n)

Longest Common Subsequence Algorithm

X and Y be two given sequences
Initialize a table LCS of dimension X.length * Y.length
X.label = X
Y.label = Y
LCS[0][] = 0
LCS[][0] = 0
Start from LCS[1][1]
Compare X[i] and Y[j]
If X[i] = Y[j]
LCS[i][j] = 1 + LCS[i-1, j-1]
Point an arrow to LCS[i][j]
Else
LCS[i][j] = max(LCS[i-1][j], LCS[i][j-1])
Point an arrow to max(LCS[i-1][j], LCS[i][j-1])

Python、Java 和 C/C++ 示例

Python Java C C++

# Python 中的最长公共子序列

# 查找最长公共子序列的函数
def lcs_algo(S1, S2, m, n):
L = [[0 for x in range(n+1)] for x in range(m+1)]

# 采用自底向上的方式构建矩阵
for i in range(m+1):
for j in range(n+1):
if i == 0 or j == 0:
L[i][j] = 0
elif S1[i-1] == S2[j-1]:
L[i][j] = L[i-1][j-1] + 1
else:
L[i][j] = max(L[i-1][j], L[i][j-1])

index = L[m][n]

lcs_algo = [""] * (index+1)
lcs_algo[index] = ""

i = m
j = n
while i > 0 and j > 0:

if S1[i-1] == S2[j-1]:
lcs_algo[index-1] = S1[i-1]
i -= 1
j -= 1
index -= 1

elif L[i-1][j] > L[i][j-1]:
i -= 1
else:
j -= 1

# 打印子序列
print("S1 : " + S1 + "\nS2 : " + S2)
print("LCS: " + "".join(lcs_algo))


S1 = "ACADB"
S2 = "CBDA"
m = len(S1)
n = len(S2)
lcs_algo(S1, S2, m, n)
// Java 中的最长公共子序列

class LCS_ALGO {
static void lcs(String S1, String S2, int m, int n) {
int[][] LCS_table = new int[m + 1][n + 1];

// 采用自底向上的方式构建矩阵
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (i == 0 || j == 0)
LCS_table[i][j] = 0;
else if (S1.charAt(i - 1) == S2.charAt(j - 1))
LCS_table[i][j] = LCS_table[i - 1][j - 1] + 1;
else
LCS_table[i][j] = Math.max(LCS_table[i - 1][j], LCS_table[i][j - 1]);
}
}

int index = LCS_table[m][n];
int temp = index;

char[] lcs = new char[index + 1];
lcs[index] = '\0';

int i = m, j = n;
while (i > 0 && j > 0) {
if (S1.charAt(i - 1) == S2.charAt(j - 1)) {
lcs[index - 1] = S1.charAt(i - 1);

i--;
j--;
index--;
}

else if (LCS_table[i - 1][j] > LCS_table[i][j - 1])
i--;
else
j--;
}

// 打印子序列
System.out.print("S1 : " + S1 + "\nS2 : " + S2 + "\nLCS: ");
for (int k = 0; k <= temp; k++)
System.out.print(lcs[k]);
System.out.println("");
}

public static void main(String[] args) {
String S1 = "ACADB";
String S2 = "CBDA";
int m = S1.length();
int n = S2.length();
lcs(S1, S2, m, n);
}
}

C 和 C++ 中最长公共子序列的示例

// C 中的最长公共子序列

#include <stdio.h>
#include <string.h>

int i, j, m, n, LCS_table[20][20];
char S1[20] = "ACADB", S2[20] = "CBDA", b[20][20];

void lcsAlgo() {
m = strlen(S1);
n = strlen(S2);

// 在矩阵中填充 0
for (i = 0; i <= m; i++)
LCS_table[i][0] = 0;
for (i = 0; i <= n; i++)
LCS_table[0][i] = 0;

// 自底向上构建矩阵
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++) {
if (S1[i - 1] == S2[j - 1]) {
LCS_table[i][j] = LCS_table[i - 1][j - 1] + 1;
} else if (LCS_table[i - 1][j] >= LCS_table[i][j - 1]) {
LCS_table[i][j] = LCS_table[i - 1][j];
} else {
LCS_table[i][j] = LCS_table[i][j - 1];
}
}

int index = LCS_table[m][n];
char lcsAlgo[index + 1];
lcsAlgo[index] = '\0';

int i = m, j = n;
while (i > 0 && j > 0) {
if (S1[i - 1] == S2[j - 1]) {
lcsAlgo[index - 1] = S1[i - 1];
i--;
j--;
index--;
}

else if (LCS_table[i - 1][j] > LCS_table[i][j - 1])
i--;
else
j--;
}

// 打印子序列
printf("S1 : %s \nS2 : %s \n", S1, S2);
printf("LCS: %s", lcsAlgo);
}

int main() {
lcsAlgo();
printf("\n");
}
// C++ 中的最长公共子序列

#include <iostream>
using namespace std;

void lcsAlgo(char *S1, char *S2, int m, int n) {
int LCS_table[m + 1][n + 1];

// 自底向上构建矩阵
for (int i = 0; i <= m; i++) {
for (int j = 0; j <= n; j++) {
if (i == 0 || j == 0)
LCS_table[i][j] = 0;
else if (S1[i - 1] == S2[j - 1])
LCS_table[i][j] = LCS_table[i - 1][j - 1] + 1;
else
LCS_table[i][j] = max(LCS_table[i - 1][j], LCS_table[i][j - 1]);
}
}

int index = LCS_table[m][n];
char lcsAlgo[index + 1];
lcsAlgo[index] = '\0';

int i = m, j = n;
while (i > 0 && j > 0) {
if (S1[i - 1] == S2[j - 1]) {
lcsAlgo[index - 1] = S1[i - 1];
i--;
j--;
index--;
}

else if (LCS_table[i - 1][j] > LCS_table[i][j - 1])
i--;
else
j--;
}

// 打印子序列
cout << "S1 : " << S1 << "\nS2 : " << S2 << "\nLCS: " << lcsAlgo << "\n";
}

int main() {
char S1[] = "ACADB";
char S2[] = "CBDA";
int m = strlen(S1);
int n = strlen(S2);

lcsAlgo(S1, S2, m, n);
}

最长公共子序列应用

  1. 在压缩基因组重新测序数据时
  2. 通过空中签名在移动电话上对用户进行身份验证