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分別表示兩個序列的長度。該過程的偽代碼如下所示:
1 LCS_LENGTH(X,Y) 2 m = length(X); 3 n = length(Y); 4 for i = 1 to m 5 c[i][0] = 0; 6 for j=1 to n 7 c[0][j] = 0; 8 for i=1 to m 9 for j=1 to n 10 if x[i] = y[j] 11 then c[i][j] = c[i-1][j-1]+1; 12 b[i][j] = '\';
13 else if c[i-1][j] >= c[i][j-1] 14 then c[i][j] = c[i-1][j]; 15 b[i][j] = '|'; 16 else
17 c[i][j] = c[i][j-1]; 18 b[i][j] = '-'; 19 return c and b
由偽代碼可以看出LCS_LENGTH運行時間為O(mn)。
4)構造一個LCS
根據第三步中保存的表b構建一個LCS序列。從b[m][n]開始,當遇到'\'時,表示xi=yj,是LCS中的一個元素。通過遞歸即可求出LCS的序列元素。書中給出了偽代碼如下所示:
1 PRINT_LCS(b,X,i,j) 2 if i==0 or j==0
3 then return
4 if b[i][j] == '\'
5 then PRINT_LCS(b,X,i-1,j-1) 6 print X[i] 7 else if b[i][j] == '|'
8 then PRINT_LCS(b,X,i-1,j) 9 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。完整程序如下所示:
1 #include <iostream> 2 using namespace std; 3 #define X_LEN 7 4 #define Y_LEN 6 5 #define EQUAL 0 6 #define UP 1 7 #define LEVEL 2 8 void lcs_length(char* X,char* Y,int c[X_LEN+1][Y_LEN+1],int b[X_LEN+1][Y_LEN+1]); 9 void print_lcs(int b[X_LEN+1][Y_LEN+1],char *X,int i,int j); 10 11 int main() 12 { 13 char X[X_LEN+1] = {' ','A','B','C','B','D','A','B'}; 14 char Y[Y_LEN+1] = {' ','B','D','C','A','B','A'}; 15 int c[X_LEN+1][Y_LEN+1]={0}; 16 int b[X_LEN+1][Y_LEN+1] = {0}; 17 int i,j; 18 lcs_length(X,Y,c,b); 19 for(i=0;i<=X_LEN;i++) 20 { 21 for(j=0;j<=Y_LEN;j++) 22 cout<<c[i][j]<<" "; 23 cout<<endl; 24 } 25 cout<<"The length of LCS is: "<<c[X_LEN][Y_LEN]<<endl; 26 cout<<"The longest common subsequence between X and y is: "<<endl; 27 print_lcs(b,X,X_LEN,Y_LEN); 28 return 0; 29 } 30 //采用動態規划方法自底向上的進行計算,尋找最優解 31 void lcs_length(char* X,char* Y,int c[X_LEN+1][Y_LEN+1],int b[X_LEN+1][Y_LEN+1]) 32 { 33 int i,j; 34 //設置邊界條件,即i=0或者j=0 35 for(i=0;i<X_LEN;i++) 36 c[i][0] = 0; 37 for(j=0;j<Y_LEN;j++) 38 c[0][j] = 0; 39 for(i=1;i<=X_LEN;i++) 40 for(j=1;j<=Y_LEN;j++) 41 { 42 if(X[i] == Y[j]) //滿足遞歸公式第二條 43 { 44 c[i][j] = c[i-1][j-1]+1; 45 b[i][j] = EQUAL ; 46 } 47 else if(c[i-1][j] >= c[i][j-1]) //遞歸公式第三條 48 { 49 c[i][j] = c[i-1][j]; 50 b[i][j] = UP; 51 } 52 else 53 { 54 c[i][j] = c[i][j-1]; 55 b[i][j] = LEVEL; 56 } 57 } 58 } 59 void print_lcs(int b[X_LEN+1][Y_LEN+1],char *X,int i,int j) 60 { 61 if(i==0 || j==0) 62 return; 63 if(b[i][j] == EQUAL) 64 { 65 print_lcs(b,X,i-1,j-1); 66 cout<<X[i]<<" "; 67 } 68 else 69 if(b[i][j] == UP) 70 print_lcs(b,X,i-1,j); 71 else 72 print_lcs(b,X,i,j-1); 73 }
程序測試結果如下所示: