最長公共子序列(LCS)

來源:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1006

一些概念:

(1)子序列: 一個序列A = a1,a2,……an,中任意刪除若干項,剩餘的序列叫做A的一個子序列。也可以認爲是從序列A按原順序保留任意若干項得到的序列。

例如:

對序列 1,3,5,4,2,6,8,7來說,序列3,4,8,7 是它的一個子序列。
對於一個長度爲n的序列,它一共有2^n 個子序列,有(2^n – 1)個非空子序列。

請注意:子序列不是子集,它和原始序列的元素順序是相關的。

(2)公共子序列 : 顧名思義,如果序列C既是序列A的子序列,同時也是序列B的子序列,則稱它爲序列A和序列B的公共子序列。

例如:

對序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 來說

序列1,8,7是它們的一個公共子序列。

請注意: 空序列是任何兩個序列的公共子序列。
例如: 序列1,2,3和序列4,5,6的公共子序列只有空序列。

(3)最長公共子序列

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 。
請注意: 最長公共子序列不唯一。

請大家用集合的觀點來理解這些概念,子序列、公共子序列以及最長公共子序列都不唯一,所以我們通常說一個最長公共子序列,但顯然最長公共子序列的長度是一定的。

最長公共子序列問題就是求序列A= a1,a2,……an, 和B = b1,b2,……bm,的一個最長公共子序列。

因爲最長公共子序列不唯一,讓我們把問題簡化,如何求出兩個序列的最長公共子序列長度呢?

你首先能想到的恐怕是暴力枚舉?那我們先來看看:序列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) <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)
今天對LCS的講解就到這裏,聰明的你是不是已經蠢蠢欲動要AC問題啦? 心動不如行動,趕快吧。

輸入

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

輸出

輸出最長的子序列,如果有多個,隨意輸出1個。

輸入示例

abcicba
abdkscab

輸出示例

abca

我的代碼:
#include <iostream>
#include <cstring>
#include <iomanip>
using namespace std;
int LCS[1020][1020],mark[1020][1020];
string s1,s2,s,ans;
void output(int x,int y)
{
    if(x<=0||y<=0)return;
    else if(mark[x][y]==1)
    {
        output(x-1,y-1);
        cout<<s1[x-1];
    }
    else if(mark[x][y]==-1)
        output(x-1,y);
    else output(x,y-1);
}
int main()
{
    cin>>s1>>s2;
    for(int i=0;i<=s1.length();i++)
    {
        for(int j=0;j<=s2.length();j++)
        {
            if(i==0)
            {
                LCS[i][j]=0;
                mark[i][j]=-1;
            }
            else if(j==0)
            {
                LCS[i][j]=0;
                mark[i][j]=0;
            }
            else if(s1[i-1]==s2[j-1])
            {
                LCS[i][j]=LCS[i-1][j-1]+1;
                mark[i][j]=1;
            }
            else
            {
                if(LCS[i-1][j]>LCS[i][j-1])
                {
                    LCS[i][j]=LCS[i-1][j];
                    mark[i][j]=-1;
                }
                else
                {
                    LCS[i][j]=LCS[i][j-1];
                    mark[i][j]=0;
                }
            }
        }
    }
    output(s1.length(),s2.length());
    cout<<endl;
    return 0;
}
發佈了28 篇原創文章 · 獲贊 17 · 訪問量 6029
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章