最长公共子序列 (LCS) 详解+例题模板(全)

您所在的位置:网站首页 递推公式例题 最长公共子序列 (LCS) 详解+例题模板(全)

最长公共子序列 (LCS) 详解+例题模板(全)

2024-07-11 15:14| 来源: 网络整理| 查看: 265

欢迎访问https://blog.csdn.net/lxt_Lucia~~ 宇宙第一小仙女\(^o^)/~萌量爆表求带飞=≡Σ((( つ^o^)つ~ dalao们点个关注呗~

 

--------------------------------我只是一条可爱哒分界线-------------------------------

 

1.摘要:

继上篇最长上升子序列后,本篇主要讲述最长公共子序列 (LCS) 。

 

2.LCS定义:

       最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列 S ,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。

       如果觉得抽象不好理解,那么咱们还是采用学习LIS的时候的方式。首先,让我们先来看一下子串、子序列还有公共子序列的概念(在上篇LIS中也曾涉及过) ,我们以字符子串和字符子序列为例,更为形象,也能顺带着理解字符的子串和子序列:

     (1)字符子串:指的是字符串中连续的n个字符,如abcdefg中,ab,cde,fg等都属于它的字串。

     (2)字符子序列:指的是字符串中不一定连续但先后顺序一致的n个字符,即可以去掉字符串中的部分字符,但不可改变其前后顺序。如abcdefg中,acdg,bdf属于它的子序列,而bac,dbfg则不是,因为它们与字符串的字符顺序不一致。

       (3)  公共子序列:如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序列A和序列B的公共子序列。如对序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 来说,序列1,8,7是它们的一个公共子序列。

       那么现在,我们再通俗的总结一下最长公共子序列(LCS):就是A和B的公共子序列中长度最长的(包含元素最多的) 仍然用序列1,3,5,4,2,6,8,7和序列1,4,8,6,7,5为例,它们的最长公共子序列有1,4,8,7和1,4,6,7两种,但最长公共子序列的长度是4。由此可见,最长公共子序列(LCS)也不一定唯一。       请大家用集合的观点来理解这些概念,子序列、公共子序列以及最长公共子序列都不唯一,所以我们通常特判取一个最长公共子序列,但很显然,对于固定的两个数组,虽然最LCS不一定唯一,但LCS的长度是一定的。查找最长公共子序列与查找最长公共子串的问题不同的地方在于:子序列不需要在原序列中占用连续的位置。最长公共子串(要求连续)和最长公共子序列是不同的。

那么该如何求出两个序列的最长公共子序列长度呢?请继续往下看~

 

3.LCS长度求法:

       你首先能想到的恐怕是暴力枚举?那我们先来看看:序列A有 2^n 个子序列,序列B有 2^m 个子序列,如果任意两个子序列一一比较,比较的子序列高达 2^(n+m) 对,这还没有算具体比较的复杂度。或许你说,只有长度相同的子序列才会真正进行比较。那么忽略空序列,我们来看看:对于A长度为1的子序列有C(n,1)个,长度为2的子序列有C(n,2)个,……长度为n的子序列有C(n,n)个。对于B也可以做类似分析,即使只对序列A和序列B长度相同的子序列做比较,那么总的比较次数高达:C(n,1)*C(m,1)*1 + C(n,2) * C(m,2) * 2+ …+C(n,p) * C(m,p)*p,其中p = min(m, n)。

       吓着了吧?怎么办?我们试试使用动态规划算法!

       我们用Ax表示序列A的连续前x项构成的子序列,即Ax= a1,a2,……ax, By= b1,b2,……by, 我们用LCS(x, y)表示它们的最长公共子序列长度,那原问题等价于求LCS(m,n)。为了方便我们用L(x, y)表示Ax和By的一个最长公共子序列。让我们来看看如何求LCS(x, y)。我们令x表示子序列考虑最后一项

(1) Ax = By

         那么它们L(Ax, By)的最后一项一定是这个元素!

       为什么呢?为了方便,我们令t = Ax = By, 我们用反证法:假设L(x,y)最后一项不是t,则要么L(x,y)为空序列(别忘了这个),要么L(x,y)的最后一项是Aa=Bb ≠ t, 且显然有a < x, b < y。无论是哪种情况我们都可以把t接到这个L(x,y)后面,从而得到一个更长的公共子序列。矛盾!        如果我们从序列Ax中删掉最后一项ax得到Ax-1,从序列By中也删掉最后一项by得到By-1,(多说一句角标为0时,认为子序列是空序列),则我们从L(x,y)也删掉最后一项t得到的序列是L(x – 1, y - 1)。为什么呢?和上面的道理相同,如果得到的序列不是L(x - 1, y - 1),则它一定比L(x - 1, y - 1)短(注意L(,)是个集合!),那么它后面接上元素t得到的子序列L(x,y)也比L(x - 1, y - 1)接上元素t得到的子序列短,这与L(x, y)是最长公共子序列矛盾。因此L(x, y) = L(x - 1, y - 1) 最后接上元素t,LCS(Ax, By) = LCS(x - 1, y - 1) + 1。

(2)  Ax ≠ By

        仍然设t = L(Ax, By), 或者L(Ax, By)是空序列(这时t是未定义值不等于任何值)。则t  ≠ Ax和t  ≠ By至少有一个成立,因为t不能同时等于两个不同的值嘛!

(2.1)如果t  ≠ Ax,则有L(x, y)= L(x - 1, y),因为根本没Ax的事嘛。

            LCS(x,y) = LCS(x – 1, y) (2.2)如果t  ≠ By,l类似L(x, y)= L(x , y - 1)

            LCS(x,y) = LCS(x, y – 1)        可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y) = max(LCS(x – 1, y) , LCS(x, y – 1))。看看目前我们已经得到了什么结论:

LCS(x,y) =  (1) LCS(x - 1,y - 1) + 1      (Ax = By) (2) max(LCS(x – 1, y) , LCS(x, y – 1))    (Ax ≠ By) 这时一个显然的递推式,光有递推可不行,初值是什么呢?显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:

LCS(x,y) =  (1) LCS(x - 1,y - 1) + 1 如果Ax = By (2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By (3) 0 如果x = 0或者y = 0 到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + 1)行(m + 1)列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(,)。

大概的伪代码如下: 输入序列A, B长度分别为n,m,计算二维表 LCS(int,int):

for x = 0 to n do for y = 0 to m do if (x == 0 || y == 0) then LCS(x, y) = 0 else if (Ax == By) then LCS(x, y) = LCS(x - 1,y - 1) + 1 else LCS(x, y) = ) max(LCS(x – 1, y) , LCS(x, y – 1)) endif endfor endfor

注意: 我们这里使用了循环计算表格里的元素值,而不是递归,如果使用递归需要已经记录计算过的元素,防止子问题被重复计算。 现在问题来了,我们如何得到一个最长公共子序列而仅仅不是简单的长度呢?其实我们离真正的答案只有一步之遥!

仍然考虑那个递推式,我们LCS(x,y)的值来源的三种情况:

(1) LCS(x – 1,  y – 1) + 1如果Ax = By 这对应L(x,y) = L(x,- 1 y- 1)末尾接上Ax

(2.1) LCS(x – 1, y)  如果Ax ≠ By且LCS(x – 1, y) ≥LCS(x, y – 1) 这对应L(x,y)= L(x – 1, y) (2.2) LCS(x, y – 1)  如果Ax ≠ By且LCS(x – 1, y) 是序列X = < x1, x2, ..., xm >的子序列当且仅当存在严格上升的序列< i1, i2, ..., ik >,使得对j = 1, 2, ... ,k, 有xij = zj。比如Z = < a, b, f, c > 是X = < a, b,c, f, b, c >的子序列。现在给出两个序列X 和Y,你的任务是找到X 和Y 的最大公共子序列,也就是说要找到一个最长的序列Z,使得Z 既是X 的子序列也是Y 的子序列。

其实就是模板题啦~求LCS长度,当A[i]=A[j]时d(I,j)d(i-1,j-1)+1,否则d(i,j)=max{ d(i-1,j),d(i,j-1) },时间复杂度为O(n*m),其中n和m分别是序列A和B的长度。

 

代码:

#include #include #include #include #include #include using namespace std; char a[1001], b[1001]; int dp[1001][1001], len1, len2; void lcs(int i,int j) { for(i=1; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3