動態規劃求解最長公共子序列

前言

推出一個新系列,《看圖輕鬆理解數據結構和算法》,主要使用圖片來描述常見的數據結構和算法,輕鬆閱讀並理解掌握。本系列包括各類堆、各類隊列、各類列表、各類樹、各類圖、各類排序等等幾十篇的樣子。mysql

最長公共子序列

最長公共子序列,英文爲Longest Common Subsequence,縮寫LCS。一個序列,若是是某兩個或多個已知序列的最長子序列,則稱爲最長公共子序列。算法

另外,要注意的是最長公共子序列與最長公共子串不同,下面看一個例子就明白。sql

有序列S1和S2,其中S1=hello,S2=hero。那麼最長公共子序列爲heo,而最長公共子串爲he。能夠看到區別就在於一個容許不連續,一個要求必須連續,而共同特色就是都要保持順序性。緩存

暴力窮舉法

暴力窮舉法是最簡單粗暴且直觀的解決方法,既然是暴力了那效率確定是最差。有X_m=<x_1,x_2,…,x_m>Y_n=<y_1,y_2,…,y_n>兩個序列,窮舉過程首先要枚舉全部可能的子序列,對於序列X,它的子序列數量達到2^m,所以這部分的時間複雜度達到O(2^m)。而每一個子序列去匹配序列Y的時間複雜度爲O(n),因此整個過程的時間複雜度爲O(n*2^m)。也就是說暴力窮舉法的時間複雜度達到指數級,而實際中序列長度可能較長,這時幾乎沒法使用該方法。網絡

子序列的數量爲什麼是2^m?某個序列的全部子序列能夠當作是從某序列中移除若干個(0到m個)元素後組成的序列,好比ABC,移除0個元素時爲{ABC},移除1個元素時爲{BC,AC,AB},移除2個元素時爲{C,B,A},移除3個元素時爲空。數據結構

暴力窮舉大體步驟:併發

  1. 對於序列X,枚舉全部子序列;
  2. 對第1步中每一個子序列匹配序列Y,記錄匹配上的最長子序列;

動態規劃

鑑於暴力窮舉法的時間複雜度太大,須要另一種方法解決該問題,動態規劃。通常在能用動態規劃解決的問題須要符合三個特徵:最優子結構、重疊子問題和無後效性。剛恰好,最長公共子序列問題符合動態規劃特徵,下面對該問題具體分析。機器學習

最優子結構

假設有X_m=<x_1,x_2,…,x_m>Y_n=<y_1,y_2,…,y_n>兩個序列,記X、Y兩個序列對應的最長公共子序列爲LCS(X_m,Y_n),肯定LCS(X_m,Y_n)的過程就是一個最優化問題。爲了分析最優子結構,咱們須要從序列X與序列Y的最後一個元素開始。分兩種狀況:數據結構和算法

  • 若是x_m=y_n,即序列X與序列Y兩個序列的最後一個元素相同,說明該元素必定是公共子序列的最後一個元素,此時原問題的狀態轉換公式爲LCS(X_m,Y_n) =LCS(X_{m-1},Y_{n-1}) +X_m。能夠看到這種狀況下,原問題已經成功分解成子問題,並且每一個階段的最優解均可以經過子問題的最優解獲得,符合最優子結構。學習

  • 若是x_m \neq y_n,即序列X與序列Y兩個序列的最後一個元素不相同,此時須要考慮兩種狀況:

    1. 假如x_m不是最長公共子序列的最後一個元素,則問題的狀態轉換公式爲LCS(X_m,Y_n) =LCS(X_{m-1},Y_{n}),即從X_m=<x_1,x_2,…,x_{m-1}>Y_n=<y_1,y_2,…,y_n>兩個序列中找。
    2. 假如y_n不是最長公共子序列的最後一個元素,則問題的狀態轉換公式爲LCS(X_m,Y_n) =LCS(X_{m},Y_{n-1}),即從X_m=<x_1,x_2,…,x_m>Y_n=<y_1,y_2,…,y_{n-1}>兩個序列中找。

以上,成功將原問題分解成子問題,並且子問題的最優解最終組成整個問題的最優解,也就是說該問題具有最優子結構性質。

重疊子問題

通過以上分析,咱們將原問題分解成三個子問題:

  1. LCS(X_m,Y_n) =LCS(X_{m-1},Y_{n-1}) +X_m
  2. LCS(X_m,Y_n) =LCS(X_{m-1},Y_{n})
  3. LCS(X_m,Y_n) =LCS(X_{m},Y_{n-1})

從中能夠看出來子問題是存在重疊的,好比對於LCS(X_{m-1},Y_{n}),當序列X_{m-1}與序列Y_{n}的最後一個元素不相同時,子問題會繼續分解成LCS(X_{m-2},Y_{n-1})LCS(X_{m-1},Y_{n-1}),也就與前面的子問題LCS(X_m,Y_n) =LCS(X_{m-1},Y_{n-1}) +X_m中的LCS(X_{m-1},Y_{n-1})重疊了。

因此,原問題具有重疊子問題性質。

無後效性

從前面子問題的轉換公式能夠看出,某階段的子問題肯定後再也不受後面決策的影響,即後面過程不會影響前面已經肯定的狀態。反過來,也能夠認爲某階段的子問題最優解由以前某些狀態獲得,而不用管以前的狀態是如何獲得的。

遞歸公式

全部問題都已經分析完畢,接下去定義遞歸公式,將全部狀態及狀態轉移用遞歸公式描述清楚。

c[i,j]LCS(X_i,Y_j)的長度,其中i = 0,1,2,...mj=0,1,2,...n

c[i,j]= \left\{\begin{matrix} 
0,&  &if \quad i=0 \ or j=0\\
c[i-1,j-1]+1, & &if \quad i,j>0 \ and \ x_i=y_j \\
max(c[i,j-1],c[i-1,j])& &if \quad i,j>0 \ and \ x_i≠y_j
\end{matrix}\right.

動態規劃實現方式

動態規劃的實現方式有兩種,即自頂向下(Top-down)與自底向上(Bottom-up)。

自頂向下方式:這種方式直接使用遞歸公式計算獲得結果,問題的解可使用子問題的解遞歸地表示。另外,對於重疊的子問題能夠將其記憶化,即保存在緩存表中,每次解決子問題以前先查緩存表,若是子問題已經解決,則咱們能夠直接使用它。對於還未解決過的子問題,咱們先解決子問題,再把子問題的解存到緩存表中。

自底向上方式:相對於自頂向下,咱們能夠反向找到另一種方式,以自底向上的方式從新構造問題。咱們不直接解決原問題,而是先嚐試解決子問題,而後經過子問題解決更大的子問題,不斷向着更大問題迭代從而解決最終的問題。

文章太長,自頂向下方式先不發出來。

自底向上方式

在實際工程中自底向上方式可使用一個二維表格來記錄最長公共子序列的求解過程。

如今有序列X=HELLO,序列Y=HERO,用動態規劃自底向上方式來求解它們的最長公共子序列。最開始時初始化整張表格,注意表格的兩個維度都比各自序列長度大一維,多出的一維用於保存初始狀態,初始狀態都爲0,在計算過程當中要用到這些初始狀態。

image

構建子問題表格

從序列X的第一個元素開始,與序列Y的第一個元素對比,由於H=H,因此LCS(1,1)=LCS(0,0)+1=1。

image

接着H!=E,因而LCS(1,2)=max(LCS(1, 1), LCS(0, 2)),即LCS(1,2)=LCS(1,1)=1。

image

image

接着H!=R,因而LCS(1,3)=max(LCS(1, 2), LCS(0, 3)),即LCS(1,3)=LCS(1,2)=1。

image

image

接着H!=O,因而LCS(1,4)=max(LCS(1, 3), LCS(0, 4)),即LCS(1,4)=LCS(1,3)=1。

image

image

一樣地,對於序列X的第二個元素也分別與序列Y的每一個元素對比,並將結果填入對應表格中,序列X的第二個元素對比完的結果以下。

image

序列X的第三個元素對比完的結果以下。

image

序列X的第四個元素對比完的結果以下。

image

序列X的第五個元素對比完,也就是最終的結果以下。

image

因此能夠看到LCS(5,4)=3,也就是說序列X和序列Y的最長公共子序列的長度爲3。

同時也能夠看到,經過動態規劃自底向上方法咱們只須要構建一張表格就能夠獲得最終的結果,而構建表格的時間複雜度爲O(m*n),時間複雜度大大下降。

構建最長公共子序列

有時候獲得最長公共子序列的長度還不能知足咱們的要求,咱們想進一步獲得長公共子序列,這時就須要依據已經構建好的表格反推回去,最終獲得結果。

也就是說先判斷兩序列的指定元素是否相同,若是相同則斜着往回走一格,但若是不相同能夠則往左或往上走一格,根據值的大小決定往左仍是往上,值一樣大的話則往左往上均可以。

接着上面的例子,通過前面過程後,表格已經構建成功,而且獲得了最長公共子序列的長度。接下去咱們來獲取最長公共子序列。從最後一格開始,

image

由於O=O,因此O屬於最長公共子序列的元素,將其輸出,接着斜着往回走一格。

image

由於L!=R,因此二者都不屬於最長公共子序列的元素,並且往左往上的值都相等,可任意選擇,這裏選擇往上走一格。

image

繼續,由於L!=R,因此二者都不屬於最長公共子序列的元素,並且往左往上的值都相等,可任意選擇,這裏選擇往上走一格。

image

繼續,由於E!=R,因此二者都不屬於最長公共子序列的元素,其中左邊的值大於上邊的值,選擇往左走一格。

image

繼續,由於E=E,因此E屬於最長公共子序列的元素,將其輸出,接着斜着往回走一格。

image

繼續,由於H=H,因此H屬於最長公共子序列的元素,將其輸出,此時已經走到盡頭,如今全部輸出的便是最長公共子序列,即HEO。而構建最長公共子序列的時間複雜度爲O(m+n)。

image

-------------推薦閱讀------------

個人開源項目彙總(機器&深度學習、NLP、網絡IO、AIML、mysql協議、chatbot)

爲何寫《Tomcat內核設計剖析》

2018彙總數據結構算法篇

2018彙總機器學習篇

2018彙總Java深度篇

2018彙總天然語言處理篇

2018彙總深度學習篇

2018彙總JDK源碼篇

2018彙總Java併發核心篇

2018彙總讀書篇


跟我交流,向我提問:

歡迎關注:

相關文章
相關標籤/搜索