最长公共子序列LCS
问题定义
-
子序列
\[对于给定序列X=<x_1,x_2,...,x_m>,Z=<z_1,z_2,...z_k>\\ 存在一个严格递增的X的下标序列<i_1,i_2,...,i_k>,对所有j=1,2,...,k\\ 满足x_{i_j}=z_j \]则称Z为X的子序列
-
公共子序列
Z既是X的子序列,也是Y的子序列
-
问题描述
给定两个序列
\[X=<x_1,x_2,...,x_m>,Y=<y_1,y_2,...,y_n> \]求X和Y的长度最长的公共子序列
应用动态规划算法
1. 刻画最长公共子序列的特征
LCS最优子结构
令$$X=<x_1,x_2,...,x_m>,Y=<y_1,y_2,...,y_n>$$为两个序列,$$Z=<z_1,z_2,...,z_k>$$是X和Y的任意LCS
- 如果\(x_m=y_n,则z_k=x_m=y_n且Z_{k-1}是X_{m-1}和Y_{n-1}的一个LCS\)
- 如果\(x_m\ne y_n,那么z_k\ne x_m\implies Z是X_{m-1}和Y的一个LCS\)
- 如果\(x_m\ne y_n,那么z_k\ne y_n\implies Z是X和Y_{n-1}的一个LCS\)
2. 一个递归解
\[c[i,j]=\begin{cases}
0\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\quad\;\;若i=1或j=0\\
c[i-1,j-1]+1\quad\quad\quad\quad\;\;\,若i,j>0且x_i=y_i\\
max(c[i,j-1],c[i-1,j])\quad 若i,j>0且x_i\ne y_i
\end{cases}
\]
3. 计算LCS的长度
LCS-LENGTH(X,Y)
m <- X.length
n <- Y.length
let c[0...m][0...n] be new table
for i<-1 to m do
c[i,0] <- 0
for i<-1 to n do
c[0,j] <- 0
for i<-1 to m do
for j<-1 to n do
if X[i]==Y[j] do
c[i][j] <- c[i-1][j-1] + 1
else
c[i][j] <- max{c[i-1][j], c[i][j-1]}
return c
4. 构造LCS
这里没有使用多余的表来存储顺序,而是通过\(c[i-1][j],c[i][j-1],c[i-1][j-1]\)的大小关系来判断顺序
- 如果\(c[i][j]==0\),则结束
- 如果\(c[i-1][j]==c[i][j]\),则对\(i=i-1,j=j\)继续
- 如果\(c[i][j-1]==c[i][j]\),则对\(i=1,j=j-1\)继续
- 如果\(c[i-1][j-1]+1==c[i][j]\),则对\(i=i-1,j=j-1\)继续,并输出\(X[i]\)
PRINT-LCS(X, c, i, j)
if c[i][j] == 0 do
return
/* 如果c[i-1][j]==c[i][j],则对i=i-1,j=j继续 */
if c[i-1][j] == c[i][j] do
PRINT-LCS(X, c, i-1, j)
/* 如果c[i][j-1]==c[i][j],则对i=i,j=j-1继续 */
else if c[i][j-1] == c[i][j] do
PRINT-LCS(X, c, i, j-1)
/* 如果c[i-1][j-1]+1==c[i][j],则对i=i-1,j=j-1继续 */
else
PRINT-LCS(X, c, i-1, j-1)
print(X[i])
代码实现
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int my_max(int a, int b) {
return (a >= b) ? a : b;
}
vector<vector<int>> lcs_length(string X, string Y)
{
int m = X.size();
int n = Y.size();
vector<vector<int>>c(m+1);
for (int i = 0; i <= m; i++) {
c[i].resize(n+1);
}
for (int i = 1; i <= m; i++) {
c[i][0] = 0;
}
for (int j = 1; j <= n; j++) {
c[0][j] = 0;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
/* 注意对应X和Y的索引 */
if (X[i-1] == Y[j-1]) {
c[i][j] = c[i - 1][j - 1] + 1;
}
else {
c[i][j] = my_max(c[i][j - 1], c[i - 1][j]);
}
}
}
return c;
}
void print_lcs(string X, vector<vector<int>> c, int i, int j)
{
if (c[i][j] == 0) {
return;
}
if (c[i-1][j] == c[i][j]) {
print_lcs(X, c, i - 1, j);
}
else if (c[i][j - 1] == c[i][j]) {
print_lcs(X, c, i, j - 1);
}
else {
print_lcs(X, c, i - 1, j - 1);
cout << X[i - 1];
}
}
int main()
{
string X;
string Y;
cin >> X;
cin >> Y;
int m = X.size();
int n = Y.size();
vector<vector<int>> c = lcs_length(X, Y);
cout << c[m][n] << endl;
print_lcs(X, c, m, n);
}
算法改进
二维数组
对于计算某个位置的\(c[i][j]\)只需要三个表项:\(c[i-1][j-1],c[i-1][j],c[i][j-1]\),所以其实只需要当前一行和上一行的表项数值,故利用利用一个二维数组记录当前行和上一行的数值即可
| \(c[i-1][j-1]\) | $c[i-1][j] $ |
|---|---|
| \(c[i][j-1]\) | $c[i][j] $ |
伪代码
/* 假设X长度m<=Y长度n */
LCS-LENGTH(X, Y)
m <- X.length
n <- Y.length
let c[2][m] be new table
for i<-1 to n do
for j<-1 to m do
if X[j] == Y[i] do
c[(i+1)%2][j] <- c[i%2][j-1]+1
else
c[(i+1)%2][j] <- max{c[(i+1)%2][j-1], c[i%2][j]}
return c[n%2][m]
代码实现
int lcs_pro(string X, string Y)
{
int m = X.size();
int n = Y.size();
vector<vector<int>> c(2);
c[1].resize(m + 1);
c[0].resize(m + 1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (X[j - 1] == Y[i - 1]) {
c[i%2][j] = c[(i-1)%2][j - 1] + 1;
}
else {
c[i%2][j] = my_max(c[i%2][j - 1], c[(i-1)%2][j]);
}
}
}
return c[(n-1)%2][m];
}
一维数组
对于计算某个位置的\(c[i][j]\)需要三个表项:
\[c[i-1][j-1],c[i-1][j],c[i][j-1]
\]
如果在一行中表示,对于某个待计算的\(c[j]\)其中:
\[c[i-1][j]=未覆盖前的c[j]\\c[i][j-1]=c[j]\\c[i-1][j-1]=覆盖之前的c[j-1]
\]
所以在一行中\(c[i-1][j]和c[i]][j-1]\)已经存在,只需要额外一个空间保存被覆盖的\(c[i-1][j-1]\)即可
| 被覆盖\(c[i-1,j-1]=pre\) | |
|---|---|
| \(c[i][j-1]=c[j-1]\) | \(c[i-1][j]=c[j]\) |
| 待写入\(c[i,j]\) |
伪代码
/* 假设X长度m<=Y长度n */
LCS-LENGTH(X, Y)
m <- X.length
n <- Y.length
let c[m] be new table
/* pre记录被前一个覆盖的值 */
pre <- 0
for i<-1 to n do
for j<-1 to m do
/* tmp临时记录被覆盖的值 */
tmp <- c[j]
if X[j] == Y[i] do
c[j] = pre+1
else
c[j] = max{c[j], c[j-1]}
pre <- tmp
return c[m]
代码实现
int lcs_plus(string X, string Y)
{
int m = X.size();
int n = Y.size();
int pre = 0;
vector<int> c(m + 1);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int tmp = c[j];
if (X[j - 1] == Y[i - 1]) {
c[j] = pre + 1;
}
else {
c[j] = my_max(c[j - 1], c[j]);
}
pre = tmp;
}
}
return c[m];
}
谁终将点燃闪电,必长久如云漂泊。

浙公网安备 33010602011771号