目录
定义
- 类似分治法的一种编程方法
最长公共子序列问题(LCS)
1.问题描述:
- 给出两个序列:X[1…m], Y[1…n],找出一个最长的公共子列;需要注意的是,最长的公共子列往往不唯一。
2. 具体举例:
- 令 X=ABCBDAB,Y=BDCABA
LCS为最长公共子列有三个:BDAB, BCBA及BCAB
注:以下将最长公共子序列简称为LCS
3. 求解
-
暴力法:考虑最坏情况下的时间复杂度。
1.先寻找X中的所有子序列,共有 2 m 2^m 2m种可能,因此时间复杂度为 O ( 2 m ) O(2^m) O(2m);
2.检查得到的子序列是否为Y的子序列,最多需与Y序列比较n个元素,因此时间复杂度为 O ( n ) O(n) O(n)
因此总时间复杂度为 O ( n ⋅ 2 m ) O(n·2^m) O(n⋅2m),非常慢!(原话是:slow)
4. 如何用更快的方法?
- 考虑如下简化问题:
a. 如何计算LCS的长度?
b. 将上述拓展求解
5. 引入以下定义:
∣ S ∣ : 为 序 列 S 的 长 度 ; \left|S\right|:为序列S的长度; ∣S∣:为序列S的长度; c [ i , j ] = ∣ L C S ( X [ 1 … i ] , Y [ 1 … j ] ) ∣ c\left[i, j\right] = \left|LCS\left(X\left[1\dots i\right], Y\left[1\dots j \right]\right)\right| c[i,j]=∣LCS(X[1…i],Y[1…j])∣
- 个人理解: c [ i , j ] c\left[i, j\right] c[i,j]表示 X [ 1 … i ] , Y [ 1 … j ] X\left[1\dots i\right], Y\left[1\dots j \right] X[1…i],Y[1…j]这两个序列的最长公共子序列的长度;因此 X , Y X,Y X,Y的最长公共子序列长度为 c [ m , n ] c\left[m, n\right] c[m,n]
6. 如何计算 c [ i , j ] c\left[i, j\right] c[i,j]?
-
可按以下公式: c [ i , j ] = { c [ i − 1 , j − 1 ] + 1 X [ i ] = Y [ j ] m a x { c [ i − 1 , j ] , c [ i , j − 1 ] } o t h e r c\left[i, j\right] = \begin {cases} c\left[i-1, j-1\right]+1 & X\left[i\right]=Y\left[j\right] \\ max\lbrace c\left[i-1, j\right], c\left[i, j-1\right] \rbrace& other \end{cases} c[i,j]={c[i−1,j−1]+1max{c[i−1,j],c[i,j−1]}X[i]=Y[j]other
-
证明:先考虑 X [ i ] = Y [ i ] X\left[i\right]=Y\left[i\right] X[i]=Y[i]的情况,引入
Z [ 1 , … , k ] = L C S ( X [ i ] , Y [ j ] ) Z\left[1,\dots ,k\right]=LCS\left( X\left[i\right],Y\left[j\right] \right) Z[1,…,k]=LCS(X[i],Y[j])由定义可得以下结论
c [ i , j ] = k c\left[i,j\right]=k c[i,j]=k Z [ k ] = X [ i ] = ( Y [ j ] ) Z\left[k\right]=X\left[i\right]=(Y\left[j\right]) Z[k]=X[i]=(Y[j])
个人理解:引入的 Z Z Z是表示具体的最长公共子序列,通过其下标可以引用到具体的字符∵ X [ i ] = Y [ j ] \because X\left[i\right]=Y\left[j\right] ∵X[i]=Y[j]
∴ 在 原 序 列 上 加 上 X [ i ] o r Y [ j ] 可 以 让 公 共 子 序 列 更 长 \therefore在原序列上加上X\left[i\right]orY\left[j\right]可以让公共子序列更长 ∴在原序列上加上X[i]orY[j]可以让公共子序列更长
∴ Z [ 1 , … , k − 1 ] 是 X [ 1 , … i − 1 ] 及 Y [ 1 , … , j − 1 ] 的 最 长 公 共 子 序 列 \therefore Z\left[1,\dots ,k-1\right]是X\left[1,\dots i-1\right]及Y\left[1,\dots ,j-1\right]的最长公共子序列 ∴Z[1,…,k−1]是X[1,…i−1]及Y[1,…,j−1]的最长公共子序列
假设存在更长的公共子序列 W W W,则 ∣ W ∣ > k − 1 \left|W\right|\gt k-1 ∣W∣>k−1,将 W ∣ ∣ Z [ k ] ( 将 两 个 字 符 合 并 ) W||Z[k](将两个字符合并) W∣∣Z[k](将两个字符合并),则 W ∣ ∣ Z [ k ] W||Z[k] W∣∣Z[k]是公共子序列,且长度 > k ( ∵ ∣ W ∣ > k − 1 ) \gt k(\because |W|>k-1) >k(∵∣W∣>k−1),与之前的 Z Z Z的定义矛盾。
∴ c [ i − 1 , j − 1 ] = k − 1 \therefore c\left[i-1,j-1\right]=k-1 ∴c[i−1,j−1]=k−1
∴ c [ i , j ] = c [ i − 1 , j − 1 ] + 1 \therefore c\left[i,j\right]=c\left[i-1,j-1\right]+1 ∴c[i,j]=c[i−1,j−1]+1对于另一种情况可通过相同方式证明,动态规划问题可以分解为子问题的最优,这体现了动态规划的一个特征,Optimal Substructure(最优子结构)
7.动态规划的特征
- Optimal Substructure(最优子结构):最优解的问题包含了子问题的最优解。
- Overlapping Subproblems(重叠子问题): 递归问题的解在子问题中重复多次。
8.递归算法的伪代码
```
LCS(x, y, i, j)
if x[i]==y[i]
then c[i, j]=LCS
else c[i, j]=max{LCS(x, y, i-1, j), LCS(x, y, i, j-1)}
return c[i, j]
```
- 举例:
取 m = 7 , n = 6 m=7, n=6 m=7,n=6,每次考虑最差的情况,即 X [ i ] 与 Y [ j ] 不 相 等 X\left[i\right]与Y\left[j\right]不相等 X[i]与Y[j]不相等,则得到以下递归树:
- 此递归树的高度: H e i g h t = m + n Height=m+n Height=m+n,因此求解整个递归树的时间复杂度为 O ( 2 m + n ) O(2^{m+n}) O(2m+n),直接求解会非常缓慢。
- 观察递归树结构,发现其中有许多分支重复,如下图框内部分,这为快速求解提供了思路,同时这也是动态规划问题的另一个特征,Overlapping Subproblems(重叠子问题)。因此实际只包含了
m
×
n
m\times n
m×n个不重复的子问题。
9.记账法(Memoization)
- 引入了记账方法计算每个单元的代价,伪代码如下:
LCS(x,y,i,j)
if c[i,j]=nil then #仅加入了这一行逻辑判断,其余与递归法相同。
if x[i]==y[j]
then c[i,j]=c[i-1,j-1]+1
else c[i,j]=max{LCS(x, y, i-1, j), LCS(x, y, i, j-1)}
10.自上向下填表(真·动态规划)
- 利用自上向下填表法进行求解:
初始化为如下表:
A | B | C | B | D | A | B | ||
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
B | 0 | |||||||
D | 0 | |||||||
C | 0 | |||||||
A | 0 | |||||||
B | 0 | |||||||
A | 0 |
根据下式自上向下进行填表:
c
[
i
,
j
]
=
{
c
[
i
−
1
,
j
−
1
]
+
1
X
[
i
]
=
Y
[
j
]
m
a
x
{
c
[
i
−
1
,
j
]
,
c
[
i
,
j
−
1
]
}
o
t
h
e
r
c\left[i, j\right] = \begin {cases} c\left[i-1, j-1\right]+1 & X\left[i\right]=Y\left[j\right] \\ max\lbrace c\left[i-1, j\right], c\left[i, j-1\right] \rbrace& other \end{cases}
c[i,j]={c[i−1,j−1]+1max{c[i−1,j],c[i,j−1]}X[i]=Y[j]other
A | B | C | B | D | A | B | ||
---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
B | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
D | 0 | 0 | 1 | 1 | 1 | 2 | 2 | 2 |
C | 0 | 0 | 1 | 2 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 1 | 2 | 2 | 2 | 3 | 3 |
B | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 |
A | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 |
- 分析
1.如何重构最长子序列:回溯法
2.空间复杂度:最小实际上可以达到 m i n { m , n } min\lbrace m,n\rbrace min{m,n}(每次计算只需要三个值的信息,与前两行无关)