LCS最长公共子串
問題介紹
LCS問題(longest common subsequence problem)指的是求解兩個字符串最長公共子序列問題。這里的子序列是可以不連續(xù)的。LCS問題廣泛地出現(xiàn)在計算生物學(xué)中(DNA序列、系統(tǒng)生成樹等等)。這里介紹如何解決LCS問題,以及算法的正確性證明和性能分析。
解決方案
假設(shè)需要求解串X,Y的LCS,其中|X|=n,|Y|=m,c[i][j]表示X[1…i]和Y[1…j]的LCS長度,z[1…k]表示X[1…i]和Y[1…j]的LCS,k=c[i][j],則問題就是求解c[n][m]和z[c[n][m]]c[n][m]和z[c[n][m]]c[n][m]和z[c[n][m]]
樸素的想法是,按照問題的要求,我們可以得到串X的所有子串,共有2n2^n2n個,然后判斷該子串是否出現(xiàn)在串Y中,每次判斷都需要遍歷串Y,因此時間復(fù)雜度為O(2n?m)O(2^n*m)O(2n?m),這顯然是我們不能接受的復(fù)雜度。
為了解決這個問題,我們需要得到該問題的一些性質(zhì):
定理:
ifX[i]==Y[j]:c[i][j]=c[i?1][j?1]+1if X[i]==Y[j]:c[i][j]=c[i-1][j-1]+1 ifX[i]==Y[j]:c[i][j]=c[i?1][j?1]+1
otherwise:c[i][j]=max(c[i?1][j],c[i][j?1])otherwise:c[i][j]=max(c[i-1][j],c[i][j-1]) otherwise:c[i][j]=max(c[i?1][j],c[i][j?1])
引理1:如果X[i]==Y[j],則z[c[i][j]]=X[i]z[c[i][j]]=X[i]z[c[i][j]]=X[i]
證明:如果z[c[i][j]]≠X[i]z[c[i][j]]\neq X[i]z[c[i][j]]?=X[i],且X[i]==Y[j],那么不妨將X[i]加入到LCS中,c[i][j]c[i][j]c[i][j]加一,因此z[1..c[i][j]]z[1..c[i][j]]z[1..c[i][j]]不是LCS,與條件矛盾,證畢。
引理2:如果X[i]==Y[j],則X[i-1]和Y[j-1]的LCS是z[1..c[i][j]?1]z[1..c[i][j]-1]z[1..c[i][j]?1]
證明:X[i-1]和Y[j-1]的LCS不是z[1..c[i][j]?1]z[1..c[i][j]-1]z[1..c[i][j]?1],則使用X[i-1]和Y[j-1]的LCS替換z[1..c[i][j]?1]z[1..c[i][j]-1]z[1..c[i][j]?1]后再加上X[i]會得到X[i]和Y[j]的一個更長的一個LCS,與條件矛盾,證畢。引理2這里展示了問題的最優(yōu)子結(jié)構(gòu)
引理3:如果兩個串的LCS包含兩個串的末尾元素X[i]和Y[j],則這兩個元素相等
證明:如果X[i]不等于Y[j],則在LCS中,X[i]對應(yīng)Y[j’],j’<j,Y[j]對應(yīng)X[i’],i’<i,這不符合公共子串保留原串順序的性質(zhì),矛盾。證畢。
當(dāng)X[i]==Y[j]時,由引理1保證了此時的LCS是串X[i]對應(yīng)串Y[j’],j′?jj'\leqslant jj′?j,對于j′<jj' < jj′<j的情況,我們不妨用jjj來替換j′j'j′,這樣也不會對LCS的長度有什么影響,然后由引理2,c[i][j]=c[i?1][j?1]+1c[i][j]=c[i-1][j-1]+1c[i][j]=c[i?1][j?1]+1
對于第二個條件,因為X[i]≠\neq?=Y[j],那么此時的LCS要么屬于c[i?1][j]c[i-1][j]c[i?1][j],要么屬于c[i][j?1]c[i][j-1]c[i][j?1]。
假設(shè)都不屬于,那么此時的LCS一定包含了c[i?1][j]c[i-1][j]c[i?1][j]中沒有的元素X[i]和c[i][j?1]c[i][j-1]c[i][j?1]中沒有的元素Y[j],由引理3,矛盾。
在證明了上述定理以后我們可以根據(jù)該式子計算LCS:
性能分析
分析遞歸樹,最壞的情況是每次x[n]!=y[m],那么會得到一個高度為n+m的二叉樹,時間復(fù)雜度為O(2n+m)O(2^{n+m})O(2n+m),空間復(fù)雜度為O(n+m)O(n+m)O(n+m)
分析上面時間復(fù)雜度我們發(fā)現(xiàn),在搜索過程中很多的子問題都是一模一樣的,也就是具有重疊子問題性質(zhì),因此我們不妨每計算出一個子問題的結(jié)果就進(jìn)行一次記錄,后面再次需要求解結(jié)果的時候就不需要再計算,而是直接返回結(jié)果。
int LCS(string& x, string &y,int n,int m,int *c) {if(-1==n || -1==m) return 0;if(c[n*y.size()+m]) return c[n*y.size()+m];int ret;if(x[n]==y[m]) ret = LCS(x, y, n-1, m-1, c)+1;else ret = max(LCS(x, y, n-1, m, c), LCS(x, y, n, m-1, c));return c[n*y.size()+m]=ret; }int main() {string X,Y;cout<<"請輸入字符串X:"; cin>>X;cout<<"請輸入字符串Y:"; cin>>Y;int* c = new int[X.size()*Y.size()+10]();cout<<"字符串X和Y的LCS的長度為"<<LCS(X, Y, X.size(), Y.size(), c)<<endl;delete c;return 0; }性能分析
我們可以把對結(jié)果是否已經(jīng)計算出的判斷和返回答案的耗費記錄在調(diào)用該狀態(tài)答案的耗費上,把實際結(jié)果的計算記錄在該狀態(tài)中,則最壞情況下每種狀態(tài)都要計算出來,因此時間復(fù)雜度為O(nm)O(nm)O(nm),空間復(fù)雜度為O(nm)O(nm)O(nm)
我們觀察計算的過程,如果我們對狀態(tài)空間按照從左向右從上向下進(jìn)行求解,就可以計算出所有的答案
int LCS(string& x, string &y,int n,int m,int *c) {for(int i=1; i<=x.size(); ++i){for(int j=1;j<=y.size(); ++j){if(x[i-1] == y[j-1])c[i*y.size()+j]=c[(i-1)*y.size()+j-1]+1;elsec[i*y.size()+j]=max(c[(i-1)*y.size()+j], c[(i)*y.size()+j-1]);}}return c[n*y.size()+m]; }性能分析
時間復(fù)雜度O(nm)O(nm)O(nm),因為訪問的是連續(xù)的內(nèi)存空間,因此這里的O(nm)O(nm)O(nm)應(yīng)該比上面小。空間復(fù)雜度O(nm)O(nm)O(nm),如果使用滾動數(shù)組還能夠?qū)⒖臻g復(fù)雜度降低為O(min(n,m))O(min(n,m))O(min(n,m)),如果不使用滾動數(shù)組,想要得到完整的LCS串需要在計算的時候設(shè)置指針,最后進(jìn)行回溯。如果使用滾動數(shù)組,需要使用分治法得到LCS串。
回溯如圖:
總結(jié)
- 上一篇: 探长欧光慈剧情介绍
- 下一篇: 将字符串中的空格用%20替换