1. 程式人生 > >動態規劃-最長公共子序列問題(LCS)

動態規劃-最長公共子序列問題(LCS)

若給定序列X={x1,x2,…,xm},則另一序列Z={z1,z2,…,zk} 是X的子序列 是指存在一個嚴格遞增下標序列{i1,i2,…,ik}使得對於所有j=1,2,…,k有:zj=xij。

例如,序列Z={B,C,D,B}是序列X={A,B,C,B,D,A,B}的子序列,相應的遞增下標序列為{2,3,5,7}。

給定2個序列XY,當另一序列Z既是X的子序列又是Y的子序列時,稱Z是序列XY公共子序列

例如,序列X={ABCBDAB}Y={BDCABA}的子序列,{BCA}XY的公共子序列,但不是最長公共子序列;{BCBA}也是XY

的公共子序列,但它是XY的最長公共子序列,因為XY沒有長度大於4的公共子序列。

給定2個序列X={x1,x2,…,xm}Y={y1,y2,…,yn},找出XY的最長公共子序列。

輸入:序列X的長度m,序列Y的長度n;序列X各個元素,序列Y各個元素

輸出:X與Y的最長公共子序列,最長公共子序列的長度。

執行結果:

解法一:窮舉法,列舉出X所有可能的子序列,並檢查它是否也是Y的子序列,從而確定它是否為公共子序列,在此過程中記錄最長的公共子序列

演算法複雜度分析: 2X中任意取l個元素構成子序列,共有2m種不同子集。     

解法二:嘗試用動態規劃求解

最長公共子序列結構

設序列X = {x1,x2,...xm}和Y= {y1,y2,...yn}的最長公共子序列為Z = {z1,z2,...zk} 則

(1)若Xm = Yn,Zk = Xm = Yn,且Zk-1是Xm-1和Yn-1的最長公共子序列。

(2)若Xm!=Yn且Zk != Xm,則Z是Xm-1和Y的最長公共子序列。(反證法:Z是Xm-1和Y的公共子序列,但不是最長的)

(3)若Xm!=Yn且Zk !=Yn,則Z是X和Yn-1的最長公共子序列。

2個序列的最長公共子序列包含了它們字首的最長公共子序列。

最長公共子序列問題有最優子結構性質。

子問題的遞迴結構

由最優子結構性質建立子問題最優值的遞迴關係

c[i][j]記錄序列XY的最長公共子序列的長度,其中, Xi={x1,x2,…,xi}Yj={y1,y2,…,yj}

i=0j=0時,空序列是XiYj的最長公共子序列。故C[i][j]=0

其他情況下,由最優子結構性質可建立遞迴關係如下。

三、計算最優值

直接利用遞迴式的演算法是指數時間的,由於在所考慮的子問題空間中,總共有θ(mn)θ是上下界符號)個不同的子問題,因此,用動態規劃演算法自底向上地計算最優值能提高演算法的效率。
b[i][j]記錄c[i][j]是由哪個子問題得到的

void LCSLength(int m, int n, char *x, char *y, int c[][maxn], int b[][maxn])
{
    int i, j;

    c[0][0] = 0;
    for(i = 1; i <= m; i++)
        c[i][0] = 0;
    for(j = 1; j <= n; j++)
        c[0][j] = 0;
    for(i = 1; i <= m; i++)
        for(j = 1; j <= n; j++)
        {
            if(x[i]== y[j])
            {
                c[i][j] = c[i-1][j-1] + 1;
                b[i][j] = 1;
            }
            else if(c[i-1][j] >= c[i][j-1])
            {
                c[i][j] = c[i-1][j];
                b[i][j] = 2;
            }
            else
            {
                c[i][j] = c[i][j-1];
                b[i][j] = 3;
            }
        }
}

四、構造最長公共子序列

LCSLength只是計算出最優值,並未給出最優解,然而陣列b可用於快速構造兩個序列的最長公共子序列:

b[i][j]=1時表示XiYj的最長公共子序列是Xi-1Yj-1的最長公共子序列加上xi所得到()

b[i][j]=2時表示XiYj的最長公共子序列Xi-1Yj的最長公共子序列相同(上)

b[i][j]=3時表示XiYj的最長公共子序列XiYj-1的最長公共子序列相同(左)

根據b的內容打印出最長公共子序列

void LCS(int i, int j, char *x, int b[][maxn])
{
    if(i == 0 || j == 0)
        return;
    if(b[i][j] == 1)
    {
        LCS(i-1, j-1, x, b);
        printf("%c ", x[i]);
    }
    else if(b[i][j] == 2)
        LCS(i-1, j, x, b);
    else
        LCS(i, j-1, x, b);
}