最长公共子序列问题
问题描述
描述:
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1, x2,…, xm>,则另一序列Z=<z1, z2,…, zk>是X的子序列是指存在一个严格递增的下标序列 <i1, i2,…, ik>,使得对于所有j=1,2,…,k有:
Xij = Zj
如果一个序列S即是A的子序列又是B的子序列,则称S是A、B的公共子序列。
求A、B所有公共子序列中最长的序列的长度。
输入:
输入共两行,每行一个由字母和数字组成的字符串,代表序列A、B。A、B的长度不超过200个字符。
输出:
一个整数,表示最长各个子序列的长度。
格式:printf("%d\n");
输入样例:
programming
contest
输出样例:
2
问题分析
设序列
X
=
{
x
1
,
x
2
,
⋯
,
x
m
}
X=\{x_1,x_2,\cdots,x_m\}
X={x1,x2,⋯,xm}
Y = { y 1 , y 2 , ⋯ , y n } Y=\{y_1,y_2,\cdots,y_n\} Y={y1,y2,⋯,yn}
Z = { z 1 , z 2 , ⋯ , z k } Z=\{z_1,z_2,\cdots,z_k\} Z={z1,z2,⋯,zk}
-
若 x m = y n x_m = y_n xm=yn则 Z k − 1 Z_{k-1} Zk−1是 X m − 1 X_{m-1} Xm−1和 Y n − 1 Y_{n-1} Yn−1的最长公共子序列;
-
若 x m ≠ y n x_m\neq y_n xm=yn,且 x m ≠ z k x_m\neq z_k xm=zk,则 Z Z Z是 X m − 1 X_{m-1} Xm−1和 Y Y Y的最长公共子序列。
-
若 y n ≠ x m y_n\neq x_m yn=xm,且 y n ≠ z k y_n\neq z_k yn=zk,则 Z Z Z是 X X X和 Y n − 1 Y_{n-1} Yn−1的最长公共子序列。
分析结论
-
当 x m = y n x_m = y_n xm=yn时,有一个子问题,即:找出 X m − 1 X_{m - 1} Xm−1和 Y n − 1 Y_{n -1} Yn−1的最长公共子序列。
-
当 x m ≠ y n x_m \neq y_n xm=yn,有两个子问题
- (1)找出 X m − 1 X_{m-1} Xm−1和 Y Y Y的最长公共子序列,
- (2)找出 X X X和 Y n − 1 Y_{n-1} Yn−1的最长公共子序列。
-
这些子问题都包含了同一个子问题( X m − 1 , Y n − 1 X_{m-1},Y_{n-1} Xm−1,Yn−1)
动态规划的三个条件:
- 最优性原理:问题( X , Y X,Y X,Y)包含的子问题( X − 1 , Y − 1 X-1,Y-1 X−1,Y−1)也是最优的,复合最优性原理。
- 无后效性:从前到后遍历字符串 X X X和 Y Y Y时,后面字符串不影响前面的最长公共序列。
- 有重叠子问题:问题( X , Y X,Y X,Y)的子问题包含了同一个子问题( X m − 1 , Y n − 1 X_{m-1},Y_{n-1} Xm−1,Yn−1)
所以最长公共子序列可以用动态规划法求解,
状态转移方程为:
c
[
i
]
[
j
]
=
{
0
,
i
=
0
,
j
=
0
c
[
i
−
1
]
[
j
−
1
]
+
1
,
x
i
=
y
i
m
a
x
{
c
[
i
]
[
j
−
1
]
,
c
[
i
−
1
]
[
j
]
}
,
x
i
≠
y
j
c[i][j]= \begin{cases} 0,\ i=0,j=0 \\ c[i-1][j-1]+1,x_i=y_i\\ max\{c[i][j-1],c[i-1][j]\},x_i \neq y_j \end{cases}
c[i][j]=⎩⎪⎨⎪⎧0, i=0,j=0c[i−1][j−1]+1,xi=yimax{c[i][j−1],c[i−1][j]},xi=yj
算法流程
源代码
#include <iostream>
#include <string>
#define MAX 100
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
int m, n;
string a, b;
int dp[MAX][MAX];
void LCSlength()
{
int i, j;
for (i = 1; i <= m;i++)
{
for (j = 1; j <= n;j++)
{
if(a[i-1] == b[j-1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
}
int main()
{
cin >> a >> b;
m = a.length();
n = b.length();
LCSlength();
cout << dp[m][n] << endl;
}
算法复杂度分析
代码中使用了两层循环而且长度分别为m和n,所以算法复杂度为 O ( m n ) O(mn) O(mn).