標籤:style blog http io color ar os for sp
1、基本概念
一個給定序列的子序列就是該給定序列中去掉零個或者多個元素的序列。形式化來講就是:給定一個序列X={x1,x2,……,xm},另外一個序列Z={z1、z2、……,zk},如果存在X的一個嚴格遞增小標序列<i1,i2……,ik>,使得對所有j=1,2,……k,有xij = zj,則Z是X的子序列。例如:Z={B,C,D,B}是X={A,B,C,B,D,A,B}的一個子序列,相應的小標為<2,3,5,7>。從定義可以看出子序列直接的元素不一定是相鄰的。
公用子序列:給定兩個序列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的一個公用子序列,但不不是最長公用子序列。
最長公用子序列(LCS)問題描述:給定兩個序列X={x1,x2,……,xm}和Y={y1,y2,……,yn},找出X和Y的最長公用子序列。
2、動態規劃解決過程
1)描述一個最長公用子序列
如果序列比較短,可以採用蠻力法枚舉出X的所有子序列,然後檢查是否是Y的子序列,並記錄所發現的最長子序列。如果序列比較長,這種方法需要指數級時間,不切實際。
LCS的最優子結構定理:設X={x1,x2,……,xm}和Y={y1,y2,……,yn}為兩個序列,並設Z={z1、z2、……,zk}為X和Y的任意一個LCS,則:
(1)如果xm=yn,那麼zk=xm=yn,而且Zk-1是Xm-1和Yn-1的一個LCS。
(2)如果xm≠yn,那麼zk≠xm蘊含Z是是Xm-1和Yn的一個LCS。
(3)如果xm≠yn,那麼zk≠yn蘊含Z是是Xm和Yn-1的一個LCS。
定理說明兩個序列的一個LCS也包含兩個序列的首碼的一個LCS,即LCS問題具有最優子結構性質。
2)一個遞迴解
根據LCS的子結構可知,要找序列X和Y的LCS,根據xm與yn是否相等進行判斷的,如果xm=yn則產生一個子問題,否則產生兩個子問題。設C[i,j]為序列Xi和Yj的一個LCS的長度。如果i=0或者j=0,即一個序列的長度為0,則LCS的長度為0。LCS問題的最優子結構的遞迴式如下所示:
3)計算LCS的長度
採用動態規劃自底向上計算解。書中給出了求解過程LCS_LENGTH,以兩個序列為輸入。將計算序列的長度儲存到一個二維數組C[M][N]中,另外引入一個二維數組B[M][N]用來儲存最優解的構造過程。M和N分別表示兩個序列的長度。該過程的虛擬碼如下所示:
LCS_LENGTH(X,Y) m = length(X); n = length(Y); for i = 1 to m c[i][0] = 0; for j=1 to n c[0][j] = 0; for i=1 to m for j=1 to n if x[i] = y[j] then c[i][j] = c[i-1][j-1]+1; b[i][j] = ‘‘; else if c[i-1][j] >= c[i][j-1] then c[i][j] = c[i-1][j]; b[i][j] = ‘↑‘; else c[i][j] = c[i][j-1]; b[i][j] = ‘←‘;return c and b
由虛擬碼可以看出LCS_LENGTH已耗用時間為O(mn)。
4)構造一個LCS
根據第三步中儲存的表b構建一個LCS序列。從b[m][n]開始,當遇到‘\‘時,表示xi=yj,是LCS中的一個元素。通過遞迴即可求出LCS的序列元素。書中給出了虛擬碼如下所示:
PRINT_LCS(b,X,i,j) if i==0 or j==0 then return if b[i][j] == ‘\‘ then PRINT_LCS(b,X,i-1,j-1) print X[i] else if b[i][j] == ‘|‘ then PRINT_LCS(b,X,i-1,j) else PRINT_LSC(b,X,i,j-1)
3、編程實現
現在採用C++語言實現上述過程,例如有兩個序列X={A,B,C,B,D,A,B}和Y={B,D,C,A,B,A},求其最長公用子序列Z。完整程式如下所示:
#include<iostream>#include<string>#include<cstring>using namespace std;#define X_LEN 7#define Y_LEN 6string s1="ABCBDAB";string s2="BDCABA";int c[X_LEN+1][Y_LEN+1];char b[X_LEN+1][Y_LEN+1];void lcs_length();void print_matrix();void print_lcs(char b[X_LEN+1][Y_LEN+1],string s,int i,int j);int main(){ memset(b,0,sizeof(b)); lcs_length(); print_matrix(); cout<<"列印子序列:"<<endl; print_lcs(b,s1,X_LEN,Y_LEN); return 0;}void lcs_length(){ int i,j; for(i=1;i<=X_LEN;i++) c[i][0]=0; for(i=0;i<=Y_LEN;i++) c[0][i]=0; for(i=1;i<=X_LEN;i++) for(j=1;j<=Y_LEN;j++) { if(s1[i-1]==s2[j-1]) { c[i][j]=c[i-1][j-1]+1; b[i][j]=‘\\‘; } else if(c[i-1][j]>=c[i][j-1]) { c[i][j]=c[i-1][j]; b[i][j]=‘|‘; } else { c[i][j]=c[i][j-1]; b[i][j]=‘-‘; } }}void print_matrix(){ int i,j; cout<<"長度矩陣:"<<endl; for(i=0;i<=X_LEN;i++) { for(j=0;j<=Y_LEN;j++) cout<<c[i][j]<<" "; cout<<endl; } cout<<"方向矩陣:"<<endl; for(i=0;i<=X_LEN;i++) { for(j=0;j<=Y_LEN;j++) cout<<b[i][j]<<" "; cout<<endl; }}void print_lcs(char b[X_LEN+1][Y_LEN+1],string s,int i,int j){ if(i==0||j==0) return; if(b[i][j]==‘\\‘) { print_lcs(b,s,i-1,j-1); cout<<s[i-1]<<" "; } else if(b[i][j]==‘|‘) { print_lcs(b,s,i-1,j); } else print_lcs(b,s,i,j-1);}
運行結果:
可以參考:http://www.cnblogs.com/kaituorensheng/archive/2013/03/31/2992319.html
第十五章 動態規劃——最長公用子序列