動態規劃-最長公共子序列問題(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個序列X和Y,當另一序列Z既是X的子序列又是Y的子序列時,稱Z是序列X和Y的公共子序列。
例如,序列X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A}的子序列,{B,C,A}是X與Y的公共子序列,但不是最長公共子序列;{B,C,B,A}也是X與Y
給定2個序列X={x1,x2,…,xm}和 Y={y1,y2,…,yn},找出X和Y的最長公共子序列。
輸入:序列X的長度m,序列Y的長度n;序列X各個元素,序列Y各個元素
輸出:X與Y的最長公共子序列,最長公共子序列的長度。
執行結果:
解法一:窮舉法,列舉出X所有可能的子序列,並檢查它是否也是Y的子序列,從而確定它是否為公共子序列,在此過程中記錄最長的公共子序列
演算法複雜度分析: 2從X中任意取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]記錄序列X和Y的最長公共子序列的長度,其中, Xi={x1,x2,…,xi};Yj={y1,y2,…,yj}。
當i=0或j=0時,空序列是Xi和Yj的最長公共子序列。故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時表示Xi和Yj的最長公共子序列是由Xi-1和Yj-1的最長公共子序列加上xi所得到(斜) ;
b[i][j]=2時表示Xi和Yj的最長公共子序列與Xi-1和Yj的最長公共子序列相同(上) ;
b[i][j]=3時表示Xi和Yj的最長公共子序列與Xi和Yj-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);
}