版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 4.0
概念
子序列
设X = < x1, x2,…, xm >,若有1≤ i1 < i2 < … < ik ≤ m,使得 Z = < z1, z2, …, zk> = < xi1, xi2, …, xik >,则称Z 是X 的子序列, 记为Z < X。
e.g. X = < A,B,C,B,D,A,B >, Z = < B,C,B,A >, 则有Z < X。
公共子序列
设X,Y 是两个序列,且有Z < X 和Z < Y, 则称Z 是X 和Y 的公共子序列。
最长公共子序列的概念
若Z < X,Z < Y,且不存在比Z 更长的X 和Y 的公共子序列, 则称Z是X和Y 的最长公共子序列,记为Z ∈ LCS(X, Y)。
最长公共子序列往往不止一个
e.g. X = < A,B,C,B,D,A,B >, Y = < B,D,C,A,B,A >.
则 Z = < B,C,B,A >, Z’ = < B,C,A,B >, Z’’ = < B,D,A,B > 均属于LCS(X, Y) ,即X,Y 有3 个LCS。
算法
Brute-force(暴力法)
列出X 的所有长度不超过n (即|Y|)的子序列,从长到短逐一进行检查,看其是否为Y 的子序列,直到找到第一个最长公共子序列。由于X共有2^m 个子序列,故此方法对较大的m 没有实用价值。
DP(动态规划)
记Xi = < x1,…,xi >即X序列的前i个字符 (1 ≤ i ≤ m)(前缀) Yj = < y1,…,yj >即Y 序列的前j 个字符 (1≤ j≤ n)(前缀) 假定Z = < z1,…,zk > ∈ LCS(X, Y)。
- 若xm = yn(最后一个字符相同),则不难用反证法证明:
该字符必是X 与Y 的任一最长公共子序列Z(设长度为k)的 最后一个字符,即有zk = xm = yn 且显然有 Zk-1 ∈ LCS(Xm-1 , Yn-1) 即Z 的前缀Zk-1 是Xm-1 与Yn-1 的最长公共子序列。 - 若xm ≠ yn,则亦不难用反证法证明:
要么Z ∈ LCS(Xm-1, Y),要么Z ∈ LCS(X, Yn-1)。 由于zk ≠ xm 与zk ≠ yn 其中至少有一个必成立,因此: 若zk ≠ xm则有Z ∈ LCS(Xm-1 , Y), 若zk ≠ yn 则有Z ∈ LCS(X, Yn-1)。
所以
- 若xm = yn,则问题化归成求Xm-1 与Yn-1 的LCS。
(LCS(X, Y)的长度等于LCS(Xm-1, Yn-1) 的长度加1) - 若xm ≠ yn 则问题化归成求Xm-1 与Y 的LCS 及X 与Yn-1 的LCS
LCS(X , Y)的长度为:max{LCS(Xm-1, Y)的长度, LCS(X, Yn-1)的长度}
引进一个二维数组C,用C[i,j]记录Xi与Yj的LCS的长度 如果我们是自底向上进行递推计算,那么在计算C[i,j]之前, C[i-1,j-1], C[i-1,j]与C[i,j-1]均已计算出来。此时我们 根据X[i]=Y[j]还是X[i]Y[j],就可以计算出C[i,j]:
- 若X[i] = Y[j],则执行C[i,j]←C[i-1,j-1]+1;
- 若X[i] ≠ Y[j],则根据: 若C[i-1,j]≥C[i,j-1]则C[i,j]←C[i-1,j];
- 否则C[i,j]←C[i,j-1]。即有
1 | 0; 若i = 0 或j = 0; |
如下图
为了构造出LCS,使用一个m × n 的二维数组b, b[i,j] 记录C[i,j] 是通过哪一个子问题的值求得的, 以决定搜索的方向:
- 若X[i] = Y[j],,则b[i,j] 中记入“↖”;
- 若C[i-1,j] ≥ C[i,j-1],则b[i,j] 中记入“↑”;
- 若C[i-1,j] < C[i,j-1],则b[i,j] 中记入“←”;
算法伪代码
1 | LCS(X,Y,m,n,C) { |
输出一个LCS(X,Y) 的递归算法
1 | LCS_Output(b,X,i,j) { |
对上述例子调用 LCS_Output(b,X,7,6)。
下面是输出全部LCS 的C++ 代码:
1 | #include <string> |
调用
1 | int main() |
结果
1 | BCBA |
END.