问题描述:Given 2 sequences, X = x1,...,xm and Y = y1,...,yn, find a common subsequence whose length is maximum. Subsequence need not be consecutive, but must be in order.
程序思路:
使用递归的思路可以解决这个问题。设输入的两个子串为X[0…m - 1]和Y[0…n - 1],L(X[0…m - 1], Y[0…n - 1])为X和Y的最长公共子串长度。分两种情况:
1. 若子串最后一个字符匹配(即X[m – 1] == Y[n – 1]),则
L(X[0…m - 1], Y[0…n - 1])= 1 + L(X[0…m - 2], Y[0…n - 2])
2. 若子串最后一个字符不匹配(即X[m – 1] != Y[n – 1]),则
L(X[0…m - 1], Y[0…n - 1]) = Max( L(X[0…m - 2], Y[0…n - 1]), L(X[0…m - 1], Y[0…n - 2]))
但是递归的情况下,很多子情况会重复计算多次,因此可以使用动态规划来优化,这里可以使用画表格的方法来记录下子问题的解,供后面计算使用。这里的表格记录最长公共子串的长度。
例如:”ABCFGR” 和”ACKWGR”的最长公共子串表为
右下角的值就是两个子串的最长公共子串长度。
如果需要输出两个子串的最长公共子串,则需要额外的空间把每个字符存起来。步骤如下:
- 构造最长公共子串长度表L[][];
- 构造一个长为L[m][n]的string s;
- 从L[m][n]这个位置开始遍历L[][],直到m或者n为0,对于L[][]中的每个元素L[i][j],
a) 若X[i - 1] == Y[j - 1],即这个字符是最长公共子串的一个字符,放在s的末尾,往左上方移动一格,即i – 1,j – 1,继续遍历。
b) 若不等,则在表格左边或者上方选择一个较大值,移动一格,即i – 1或者j – 1,继续遍历。
根据上面的例子,给出遍历路径:
所以”ABCFGR” 和”ACKWGR”的最长公共子串为”ACGR”。
算法伪代码:
1 //LCS算法 2 //输入:子串X,子串Y,X的长度m,Y的长度n 3 //输出:最长公共子串 4 LCS(X[], Y[], m, n) 5 Begin 6 Create L[m + 1][n + 1] 7 for each i from 0 to m do 8 for each j from 0 to n do 9 if i == 0 or j == 0 10 L[i][j] = 0 11 Else if X[i – 1] == Y[j – 1] 12 L[i][j] = L[i – 1][j – 1] + 1 13 Else 14 L[i][j] = max(L[i – 1][j], L[i][j – 1]) 15 Create char c[L[m][n]] 16 For L[m][n] to L[0][p] or L[q][0] 17 If X[i - 1] == Y[j – 1] 18 Push this char to c 19 Skip to left up 20 Else 21 Skip to left or up which is larger 22 End
算法性能分析:
这个算法主要做的事情是,建立最长公共子串表,然后遍历这个表。表的建立需要一个一个填写表项,因此复杂度为O(mn)。
补充一下,之前一直没有考虑最长公共子串不唯一的问题。我们的算法的确能够找到一个最长公共子串,但是如果要把所有的串输出,在步骤二中必须分情况讨论,把中间结果压栈,具体细节有空再补充。