• 沒有找到結果。

2.4、构造最长公共子序列

下面的算法 LCS(b,X,i,j)实现根据 b 的内容打印出 Xi与 Yj的最长公共子序列。通过算法 的调用 LCS(b,X,length[X],length[Y]),便可打印出序列 X 和 Y 的最长公共子序列。

1. Procedure LCS(b,X,i,j);

2. begin

3. if i=0 or j=0 then return;

4. if b[i,j]="↖" then 5. begin

6. LCS(b,X,i-1,j-1);

7. print(x[i]); {打印 x[i]}

8. end

9. else if b[i,j]="↑" then LCS(b,X,i-1,j) 10. else LCS(b,X,i,j-1);

11. end;

在算法 LCS 中,每一次的递归调用使 i 或 j 减 1,因此算法的计算时间为 O(m+n)。

例如,设所给的两个序列为 X=<A,B,C,B,D,A,B>和 Y=<B,D,C,A,B,A>。

由算法 LCS_LENGTH 和 LCS 计算出的结果如下图所示:

我来说明下此图(参考算法导论)。在序列 X={A,B,C,B,D,A,B}和 Y={B,D,

C,A,B,A}上,由 LCS_LENGTH 计算出的表 c 和 b。第 i 行和第 j 列中的方块包含了 c[i,

j]的值以及指向 b[i,j]的箭头。在 c[7,6]的项 4,表的右下角为 X 和 Y 的一个 LCS<B,C,B,

A>的长度。对于 i,j>0,项 c[i,j]仅依赖于是否有 xi=yi,及项 c[i-1,j]和 c[i,j-1]的值,这 几个项都在 c[i,j]之前计算。为了重构一个 LCS 的元素,从右下角开始跟踪 b[i,j]的箭头即 可,这条路径标示为阴影,这条路径上的每一个“↖”对应于一个使 xi=yi 为一个 LCS 的成员 的项(高亮标示)。

所以根据上述图所示的结果,程序将最终输出:“B C B A”,或“B D A B”。

可能还是有读者对上面的图看的不是很清楚,下面,我再通过对最大子序列,最长公共 子串与最长公共子序列的比较来阐述相关问题@Orisun:

 最大子序列:最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比 如{5,-3,4,2}的最大子序列就是{5,-3,4,2},它的和是 8,达到最大;而{5,-6,4,2}的最大 子序列是{4,2},它的和是 6。你已经看出来了,找最大子序列的方法很简单,只要 前 i 项的和还没有小于 0 那么子序列就一直向后扩展,否则丢弃之前的子序列开始 新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。更多请 参看:程序员编程艺术第七章、求连续子数组的最大和。

 最长公共子串:找两个字符串的最长公共子串,这个子串要求在原字符串中是连续 的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩 阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和

"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")

b a b

c 0 0 0

a 0 1 0

b 1 0 1

a 0 1 0

我们看矩阵的斜对角线最长的那个就能找出最长公共子串。

不过在二维矩阵上找最长的由 1 组成的斜对角线也是件麻烦费时的事,下面改进:

当要在矩阵是填 1 时让它等于其左上角元素加 1。

b a b

c 0 0 0

a 0 1 0

b 1 0 2

a 0 2 0

这样矩阵中的最大元素就是最长公共子串的长度。

在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实 际上在程序中可以用一维数组来代替这个矩阵。

 最长公共子序列LCS 问题:最长公共子序列与最长公共子串的区别在于最长公共子 序列不要求在原字符串中是连续的,比如ADE 和 ABCDE 的最长公共子序列是 ADE。

我们用动态规划的方法来思考这个问题如是求解。首先要找到状态转移方程:

等号约定,C1 是 S1 的最右侧字符,C2 是 S2 的最右侧字符,S1‘是从 S1 中去除 C1 的部分,S2'是从 S2 中去除 C2 的部分。

LCS(S1,S2)等于:

(1)LCS(S1,S2’)

(2)LCS(S1’,S2)

(3)如果 C1 不等于 C2:LCS(S1’,S2’);如果 C1 等于 C2:LCS(S1',S2')+C1;

边界终止条件:如果 S1 和 S2 都是空串,则结果也是空串。

下面我们同样要构建一个矩阵来存储动态规划过程中子问题的解。这个矩阵中的每个数 字代表了该行和该列之前的LCS 的长度。与上面刚刚分析出的状态转移议程相对应,矩阵 中每个格子里的数字应该这么填,它等于以下3 项的最大值:

(1)上面一个格子里的数字

(2)左边一个格子里的数字

(3)左上角那个格子里的数字(如果 C1 不等于 C2);左上角那个格子里的数字+1(如 果C1 等于 C2)

举个例子:

G C T A

0 0 0 0 0

G 0 1 1 1 1

B 0 1 1 1 1

T 0 1 1 2 2

A 0 1 1 2 3

填写最后一个数字时,它应该是下面三个的最大者:

(1)上边的数字 2

(2)左边的数字 2

(3)左上角的数字 2+1=3,因为此时 C1==C2

所以最终结果是3。

在填写过程中我们还是记录下当前单元格的数字来自于哪个单元格,以方便最后我们回 溯找出最长公共子串。有时候左上、左、上三者中有多个同时达到最大,那么任取其中之一,

但是在整个过程中你必须遵循固定的优先标准。在我的代码中优先级别是左上>左>上。

下图给出了回溯法找出 LCS 的过程:

相關文件