LCS(longest-common-subsequence problem),又名最長公共子序列問題
給定兩個序列X和Y,若是Z既是X的子序列,也是Y的子序列,咱們稱它爲X和Y的公共子序列
好比X={A,B,C,D,E,F,G} Y={T,A,C,M,G},那麼Z={A,C,G},就是其最長公共子序列
複製代碼
咱們通常的字符串匹配,就是子串查找;可是實際應用中,每每有子串查找解決不了的問題;
好比『我家有臺電視機,你要不』和『我有個電視機,你要不』這二者其實指代同一個事情;
那麼在實際應用場景中,特別是工業領域,每每沒法存在嚴格的子串和母串;
更多的是類似性問題,即類似的字符串指代同一個物品/事件。
複製代碼
最長公共子序列問題:給定兩個序列
X={X1,X2,...,Xm} Y={Y1,Y2,...,Yn},求X和Y長度最長的公共子序列。
複製代碼
好了,咱們想想,假設這是個算法題,咱們會怎麼作?算法
第一反應多是窮舉掃描,可是呢,咱們也知道,若是字符串太長,暴力方法運行的時間是咱們不能忍受的。數組
而後咱們又會想到,將大問題拆分紅小問題,用遞歸的思想解決。post
咱們假設:
1.Xm=Yn,那麼X,Y的最長公共子序列,確定會包含最後這個字符,那麼就變成求解Xm-1,Ym-1的LCS
2.Xm!=Yn,這個時候,若是最長公共子序列不含Xm,就意味着,該題變成求解Xm-1和Yn的公共子序列
3.Xm!=Yn,這個時候,若是最長公共子序列不含Yn,就意味着,該題變成求解Xm和Yn-1的公共子序列
複製代碼
至此爲止,咱們找到了求解最長公共子序列的方法。優化
咱們仔細觀察一下上述的分析過程,是否是似曾相識? 這正是問題!spa
讓咱們來回顧一下什麼是動態規劃:code
動態規劃一般用來求解最優化問題
動態規劃是經過組合子問題的解來解決原問題
....
複製代碼
以及觀察某個問題,是否適用於動態規劃算法:cdn
1.是否具備最優子結構性質
若是一個問題的最優解,包含其子問題的最優解
2.具備重疊子問題性質
問題的遞歸算法會反覆求解相同的子問題
複製代碼
詳見《什麼是動態規劃》一章blog
好了,接着說回LCS,經過上述的分析,咱們獲得以下的公式: 遞歸
爲何公式是如上形式?由於咱們假設X,Y串以下排列,像是一個二維數組 事件
其中X={A,B,C,A,D,A,B} Y={B,A,C,D,B,A}
C[i][j]表明公共子序列的長度
若是Xi=Yj,那麼意味着子序列又多匹配上一位,其在Xi-1,Yj-1的最長公共子序列的結果上多增長了一個。
若是Xi!=Yj,那麼意味着,Xi,Yj的最長公共子序列的答案確定在Xi-1,Yj或者Xi,Yj-1的最長公共子序列中,那麼既然是最長,確定是取二者中更大的值;
至此,咱們已經清晰的定義出LCS的求解公式。
按照這個公式,咱們能夠自頂向下的遞歸或者自底向上的累加;通常動態規劃,採用自底向下更簡單些;
思路以下:
int nLenX = X.length();
int nLenY = Y.length();
char** C = new char[nLenX + 1][nLenY + 1];
//初始化c二維數組
....
//LCS
for ( int i = 1; i <= nLenX; i++ )
{
for ( int j = 1; j <= nLenY; j++ )
{
if ( X[i-1] == Y[j-1])
{
C[i][j] = C[i-1][j-1] + 1;
}
else if ( C[i-1][j] >= C[i][j-1] )
{
C[i][j] = c[i-1][j];
}
else
{
C[i][j] = C[i][j-1];
}
}
}
//析構數組
複製代碼
按照公式實現就好了,很簡單
這裏有個小細節。C[][]下標是從1開始的,而且長度比X,Y要長1位,這是從實現角度,省下了考慮i-1 < 0的問題,實現的一個小技巧。
以下圖所示:
目前爲止,咱們確實是實現了LCS,可是如何輸出該最長子串呢?
很簡單,只要在上述代碼中,對其走過的字串進行標記,標記後經過反向遞歸,獲得路徑,如上圖所示的灰色箭頭就是其回溯的路徑。
其餘相關章節
複製代碼
算法相關文章之一:《簡單說說二叉搜索樹》
算法相關文章之二:《B樹,一點都不神祕》
算法相關文章之三:《B樹很簡單,插入so easy》
算法相關文章之四:《什麼是動態規劃》
算法相關文章之五:《LCS,給你一個不同的模糊匹配》