本文作者frankchenfu,blogs網址http://www.cnblogs.com/frankchenfu/,轉載請保留此文字。
今天,我給大家帶來的是“最長公共子序列”(LCS)的講解。限於水平,這里僅介紹O(nm)算法。
最長公共子序列其實是很好理解的。
顧名思義,給出多個(這里暫且只考慮兩個)序列,求他們的最長公共子序列,就是在這兩個序列中分別刪去一些的字符,得到兩個相同的序列,使得這兩個相同的序列最長。
當然上面是我自己用比較好理解的方法寫的,關於某些“百科”上的解釋就是“一個序列S,如果分別是兩個或多個已知序列的子序列,且是所有符合此條件序列中最長的,則S稱為已知序列的最長公共子序列。而最長公共子串(要求連續)和最長公共子序列是不同的 ”。
這里要先牽扯到一個“子序列”的問題。子序列就是一個序列中,刪去一些字符后剩下部分。例如,“abc”就是“axbyc”的一個子序列,他相應的子序列在原序列的下標就是“1,3,5”。
再比如,
令字串A為“abcdef”,字串B為“defghi”,那么他們的最長公共子序列是什么呢?
顯然,是“def”。因為子串“d”、“e”和“f”長度均為1,子串“de”、“ef”和“df”均為2,只有“def”長度為3。
所以,現在,我們給出兩個序列,求他們的最長公共子序列。
【輸入格式】
共兩行。每行給出一個長度不超過200的字符串。
【輸出格式】
共兩行。第一行一個非負整數表示最長公共子序列的長度。第二行輸出這個子序列(若有多解,任意輸出一個)。
【輸入樣例】
ABCBDAB
BDCABA
【輸出樣例】
4
BCBA
【分析】
看到題目,肯定有人會想到搜索。可是搜索並不能解決一切問題。我們看到搜索是指數級的時間復雜度——你給10秒能不能算完是問題!我們需要設計一個略微高效的算法——嘗試使用動態規划。但是在此之前,我們先來看一些有效分析,來驗證我們的猜想是否正確。
(1)最優子結構性質。
這個性質有一個定理,叫LCS的最優子結構性質。
這個定理告訴我們,兩個序列的最長公共子序列包含這兩個序列的前綴最長公共子序列,所以這個問題是符合最優子結構性質的。
(2)子問題重疊性質
由最優子結構性質可以推導出該問題具備子問題重疊性質。我們可以依此建立一個計算到目前為止,子序列“最優值”的問題的遞歸關系。用c[i][j]記錄序列X[i]和Y[i]的最長公共子序列的長度,其中當$i=0$或$j=0$時,$c_{i,j}=0$。否則,當$X_i=Y_i$時,$c_{i,j}=c_{i-1,j-1}+1$,當$X_i$≠$Y_i$時,$c_{i,j}=max(c_{i,j-1},c_{i-1,j})$.
這些方法得出的時間復雜度已經滿足200的數據了,不過可以再優化,將體現在代碼中,大家可以自行思考。Cpp代碼如下:
#include<cstdio> #include<cstring> #include<string> using namespace std; inline int max(int a,int b) { return a>b?a:b; } const int MAX=201; int c[MAX][MAX]; int n,i,j,l1,l2; char x[MAX],y[MAX]; string z=""; int main() { scanf("%s%s",x,y); l1=strlen(x);l2=strlen(y); for(i=1;i<=l1;i++) for(j=1;j<=l2;j++) if(x[i-1]==y[j-1]) c[i][j]=c[i-1][j-1]+1; else c[i][j]=max(c[i-1][j],c[i][j-1]); printf("%d\n",c[l1][l2]); //求LCS長度已完成,若無需求內容,可忽略以下。 for(i=l1,j=l2;i&&j;) if(x[i-1]==y[j-1]) { z=x[--i]+z; j--; } else if(c[i-1][j]>c[i][j-1]) i--; else j--; printf("%s",z.c_str());//返回C風格可用%s的字符串 return 0; }
希望對大家有所幫助,謝謝!