lcs是longest common subsequence的縮寫,即最長公共子串行。乙個序列,如果是兩個或多個已知序列的子串行,且是所有子串行中最長的,則為最長公共子串行。
另外還有個分支問題:最長公共子串。子串的字元位置必須連續,而子串行則不必,從原序列中去掉任意的元素獲得的新序列。可以看出,子串問題比子串行問題要簡單地多,子串必定是子串行,換言之,子串是子串行的子集。如果我們能解決子串行問題,子串問題也迎刃而解。
窮舉法是顯而易見第一時間從腦子裡蹦出來的想法,實際上**層面的實現也不困難。提取出a序列的每乙個子串行,檢查其是否也是b序列的子串行,全部比對完後,比較出最長的乙個子串行。
不考慮子串行重複的前提下啊,乙個長度為n的序列,其子序列個數為2^n(容易理解,每一項取或不取)。易知其時間複雜度為o(2^n),指數級複雜度一般來說是不可接受的。
這裡的空間複雜度我看一些文章說也是o(2^n),但是我覺得並不需要存下每乙個子串行,每乙個a的子串行經驗證不是b的子串行後即可丟棄,所以儲存的花費並不是所有子串行,而是所有公共子串行。所以我認為空間複雜度沒有達到o(2^n),可能是我的理解有問題,如果有懂得觀眾看到這裡,懇請指點一二。
記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的最長公共子串行;
3. 若xm≠yn且zk≠yn,則z是x和yn-1的最長公共子串行。
其中xm-1 = [x1, x2, …, xm-1]
,yn-1 = [y1, y2, …, yn-1]
,zk-1 = [z1, z2, …, zk-1]
。
第2點和第3點可以合併為,max(lcs(xm-1,yn),lcs(xm,yn-1))
。
記乙個二維陣列c
,c[i,j]儲存xi和yi的最長公共子串行的長度。所以c[m,n]即矩陣最右下角的值為x與y的最長公共子串行的長度。
雖然我們在遞推過程是從序列的尾部開始的,但實際解題是從頭部開始的,因為在計算max(lcs(xm-1,yn),lcs(xm,yn-1))
時,需要事先計算出lcs(xm-1,yn)
和lcs(xm,yn-1)
,才能比較他們的大小。
1. 先令c[i,0]整一列的值為0,顯然任意序列與空序列的最長公共子串行長度為0;同理,令c[0,j]整一行的值為0;
2. 如果當前比較的兩個字元xi=yj
,令這個格仔的c[i,j] = 1
。方向為左上角(lefttop);
3. 如果當前比較的兩個字元xi≠yj
,比較c[i-1,j]和c[i,j-1]的值,取其中較大的值填充入c[i,j]中,方向為值的**方向左(left)或者上(top);
4. 一直迭代運算至二維陣列c所有格仔均有值,結束。
便於理解抄自網路的圖:
記錄方向是為了構造出最長公共子串行,當然這樣的演算法有乙個侷限就是當lcs(xm-1,yn) = lcs(xm,yn-1)
時會出現多解,即最長公共子串行不唯一。這樣的情況顯然是可預見的,所以在當出現lcs(xm-1,yn) = lcs(xm,yn-1)
時兩個方向都得記錄,才能恢復出所有的最長公共子串行(如果有需要)。
當然,如果只是為了求得最長公共子串行的長度,方向是不必記錄的。連矩陣都可以不用構造,因為c[i,j]的值完全**於上一行的值,即c[i-1,j-1]、c[i-1,j]、c[i,j-1]三者其中之一,只需要記錄矩陣中的兩行資料即可,空間複雜度進一步降低。
解決了最長公共子串行問題,最長公共子串就簡單地多了。仍然是構造二維矩陣c
,當xi = yj
時,令c[i,j] = c[i-1,j-1]
,然後矩陣中最大的元素就是最長公共子串的長度。構造最長公共子串也只需要找出最長的一條斜對角線即可。
附python實現:
def
find_lcs_len
(input_x, input_y):
dp = [([0] * len(input_y)) for i in range(len(input_x))]
maxlen = 0
for i in range(0, len(input_x)):
for j in range(0, len(input_y)):
if input_x[i] == input_y[j]:
if i != 0
and j != 0:
dp[i][j] = dp[i - 1][j - 1] + 1
if i == 0
or j == 0:
dp[i][j] = 1
if dp[i][j] > maxlen:
maxlen = dp[i][j]
return maxlen
看到這有些人可能會疑惑,最長遞增子串行只關係到乙個序列。如序列x = [5,8,2,3,9,4,7]
的lis為[2,3,4,7]
。而lcs問題是兩個序列的公共子串行問題。
其實這裡先構造乙個輔助序列x' = [2,3,4,5,7,8,9]
,即對x排序生成的新序列。對序列x和x』求lcs就是這個問題的解。這裡不再詳細論述,相信聰明的讀者都容易看懂其中邏輯。
用lcs演算法代替窮舉法來解決最長公共子串行問題,時間複雜度由o(2^n)下降到了o(n*m),空間複雜度也是同等級數的下降。經由精妙的lcs演算法,為我們方便地解決了運算起來繁複的問題。
有機會得繼續學習這些有趣奇妙的演算法。另外,我也得花時間去理解下複雜度的計算,之前一直是我的盲點。
收!
LCS問題 動態規劃
簡述 lcs問題,即最長公共子串行問題,給定兩個序列x 和y 求x y最長的公共子串行。與lis類似,lcs也是可以不連續的。解題思路 本人覺得在這個問題上演算法導論講的很好,所以在此我主要是整理。1 首先我們來考慮暴力搜尋求解的方法,我們要暴力列舉x的所有子串行,然後再看看是不是也是y的子串行,這...
動態規劃 LCS計算
int findlcs string a,int n,string b,int m dp的第一行 for int j 0 j m 1 j 其他位置的dp值 for int i 1 i n 1 i else dp i j dp i 1 j dp i j 1 dp i 1 j dp i j 1 retu...
LCS的動態規劃演算法
輸入 兩個字串 輸出 最長公共子串行 演算法思想 動態規劃 include include using namespace std string x,y 輸入串 int c 100 100 維護lcs length的陣列 int b 100 100 用於構造乙個最優解 int lcs len str...