最长公共子序列
问题就是:在给定的两个序列里,可以作为两个序列的子序列的最长的序列是什么。比如,考虑两个单词abracadabra
和batter
作为两个字母序列,这两个序列的最长公共子序列就是bar。在这个问题里,可以有多个具有相同最大长度的公共序列存在,但在这个例子里,bar
是唯一一个长度为3的公共子序列。
用暴力算法
来解决这个问题可以通过查看一个序列的所有子序列是不是另一个序列的子序列,并且一直执行下去直到找到最长的子序列。
对于长度为n的序列来说,它的子序列的数量为2^n。
要得到这个结果,可以把序列里的每个元素都看成可以选择或者不选择的元素,从而得到一个更容易分析的图像化问题。这就能够让我们把整个序列当作一个由n个二进制数组成的序列,然后计算它的子序列的数量。对于n比特来说,可以被用来表示数据的数量是2^n个,这也就是说这个得到所有的子序列的过程是Θ (2^n)的算法。之后我们需要再去找最长的子序列,它也是另一个序列的子序列。
对于你来说可能并不能够很直观地知道动态规划
可以被用来解决整个问题。这也就是我们为什么要研究各种算法技术的例子。学习解决各种不同问题的技术的经验可以帮你了解新问题应该用什么技术来解决。为了对整个问题使用动态规划,我们需要确定如何根据较小问题的解来找到较大问题的最佳解。这一步需要你的直觉和经验来帮助你。
我们的第一步是检查一个小问题,因此我们会从长度为1的序列
开始(也就是问题的基本情况
)。
比如说,对于序列
abcd
和cabe
来说,这两个序列的最长公共子序列是ab
。如果我们把字母f添加到两个序列的末尾,那么最长公共子序列就是abf
。
由于我们有了两个序列,我们将使用第一个例子里的两个序列制作一个二维表。表里的[i, j]元素会被用来指代一个序列从字母1到i的子序列,以及另一个序列从字母1到j的子序列的最长公共子序列的长度
。
我们填上了表15.4所示的第一行和第一列。
我们更新了的表15.5里填写了第二行里的数字。
我们还需要确定,如果字母一样的话需要做什么。一个例子是:把字母c加进第一个序列,从而得到abrac
。然后我们想知道abrac和ba的公共子序列的长度。由于字母c和ba的最后一个字母不一样,因此我们不能增加最长公共子序列的长度。而且,abrac和ba的最长公共子序列必然是abrac和b的最长公共子序列或者abra和ba的最长公共子序列里面更长的那个。对于我们的表格来说,这相当于在元素不一样的时候,取左边或者上边的最大值。这个例子的最终结果如表15.6所示。
在有了这个表之后,我们就可以确定最长公共子序列是什么。实现这一点的关键是:当我们在对对角线加1的时候,我们在公共子序列里添加这个字母。通过追踪用来计算长度的元素,我们就能够确定公共子序列的结果。我们还可以通过在长度里加上箭头来表示这个值是从哪里计算出的。对于我们的表格来说,我们使用字母D来表示值是从对角线元素来的,字母L表示左箭头,字母U表示向上的箭头。接下来,我们从表格的右下角开始,因此我们有数字3和向左的箭头,代表着有最长的长度为3,并且需要向左移动。然后我们能够得到对角线箭头,所以我们的公共子序列里的最后一个字母是r。我们继续按照箭头前进,同时在遇到对角线箭头的时候,就在我们的公共子序列的开头插入这个字母。当箭头指向了表格之外的时候,我们就完成了整个流程,而且找到了最长公共子序列。
对于我们的例子来说,如果我们继续刚才的流程,当我们到达了对应于abra和ba在表里的位置的时候,我们会将字母a插入公共子序列里。而后,当我们到达了对应于ab和b在表里的位置的时候,我们会将字母b插入公共子序列里。那个对角线箭头会让我们离开这个表格,从而表明我们的最长公共子序列是bar。在我们的例子里,当左侧和上方的元素相等的时候,我们会选择左侧的值。如果你的实现是选择上面的值的话,可能会得到有相同的最大长度,但是不一样的公共子序列。
这个算法的运行时分析非常简单,因为我们必须要填写整个表格,而计算每个元素只需要恒定的时间。因此,对于长度为m和n的序列来说,运行时间为Θ (mn)。对于我们的暴力算法的运行时间来说,因为我们需要创建较短序列的所有子序列,然后检查它们是不是另一个序列的子序列,因此需要至少Θ (2n)的时间,其中n是较短序列的长度。很明显,动态规划算法的效率更高。
我们的算法的一个缺点是它需要Θ (mn)
的空间来计算出结果。最长公共子序列问题的常见应用是DNA匹配,而DNA是一长串的字母。