LCS~~最长公共子序列(动态规划)

A和B的公共子序列中长度最长的(包含元素最多的)叫做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 。
请注意: 最长公共子序列不唯一。

我们用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(,)。

for(int i = 1; i <= A_len; ++i) {
	for(int j = 1; j <= B_len; ++j) {
		if(A[i - 1] == B[j - 1]) {
			dp[i][j] = dp[i - 1][j - 1] + 1;
		} else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
	}
}

仍然考虑那个递推式,我们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) <LCS(x, y – 1)
这对应L(x,y) = L(x, y – 1)


(3) 0 如果 x =0或者y = 0
这对应L(x,y)=空序列


注意(2.1)和(2.2) ,当LCS(x – 1, y) = LCS(x, y – 1)时,其实走哪个分支都一样,虽然长度时一样的,但是可能对应不同的子序列,所以最长公共子序列并不唯一。
神奇吧?又一个类似的递推公式。可见我们在计算长度LCS(x,y)的时候只要多记录一些信息,就可以利用这些信息恢复出一个最长公共子序列来。就好比我们在迷宫里走路,走到每个位置的时候记录下我们时从哪个方向来的,就可以从终点回到起点一样。


另外,说一下复杂度?


时间复杂度时O(n * m),空间也是O(n * m)


那么在这里插入一道例题:

输入

第1行:字符串A
第2行:字符串B
(A,B的长度 <= 1000)

输出

输出最长的子序列,如果有多个,随意输出1个。

输入示例

abcicba
abdkscab


输出示例

abca
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
char str1[1002],str2[1002];
int d[1002][1002];
int main()
{
    while(gets(str1) && gets(str2))
    {
        int len1=strlen(str1),len2=strlen(str2);
        memset(d,0,sizeof(d));
        for(int i=len1-1; i>=0; i--)    //这里用来用动态规划求最长公共子序列
            for(int j=len2-1; j>=0; j--)  
            {
                if(str1[i]==str2[j])
                    d[i][j]=d[i+1][j+1]+1;
                else
                    d[i][j]=max(d[i+1][j],d[i][j+1]);
            }
       int i = 0, j = 0;
        while (i < len1 && j < len2){   //这里用来输出最长公共子序列
            if (str1[i] == str2[j]){
            printf("%c",str1[i]);
                i++;
                j++;
            }
            else if (d[i+1][j] >= d[i][j+1])
                i++;
            else
                j++;
        }
        printf("\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/gao506440410/article/details/81539408
今日推荐