LCS,給你一個不一樣的模糊匹配
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},就是其最長公共子序列 複製程式碼
2. 為什麼說需要LCS來解決模糊匹配
我們一般的字串匹配,就是子串查詢;但是實際應用中,往往有子串查詢解決不了的問題; 比如『我家有臺電視機,你要不』和『我有個電視機,你要不』這兩者其實指代同一個事情; 那麼在實際應用場景中,特別是工業領域,往往無法存在嚴格的子串和母串; 更多的是相似性問題,即相似的字串指代同一個物品/事件。 複製程式碼
3. 實現原理
最長公共子序列問題:給定兩個序列 X={X1,X2,...,Xm} Y={Y1,Y2,...,Yn},求X和Y長度最長的公共子序列。 複製程式碼
好了,我們想一想,假設這是個演算法題,我們會怎麼做?
第一反應可能是窮舉掃描,但是呢,我們也知道,如果字串太長,暴力方法執行的時間是我們不能忍受的。
然後我們又會想到,將大問題拆分成小問題,用遞迴的思想解決。
我們假設: 1.Xm=Yn,那麼X,Y的最長公共子序列,肯定會包含最後這個字元,那麼就變成求解Xm-1,Ym-1的LCS 2.Xm!=Yn,這個時候,如果最長公共子序列不含Xm,就意味著,該題變成求解Xm-1和Yn的公共子序列 3.Xm!=Yn,這個時候,如果最長公共子序列不含Yn,就意味著,該題變成求解Xm和Yn-1的公共子序列 複製程式碼
至此為止,我們找到了求解最長公共子序列的方法。
我們仔細觀察一下上述的分析過程,是不是似曾相識? 這正是 問題!
讓我們來回顧一下什麼是動態規劃:
動態規劃通常用來求解最優化問題 動態規劃是通過組合子問題的解來解決原問題 .... 複製程式碼
以及觀察某個問題,是否適用於動態規劃演算法:
1.是否具有最優子結構性質 如果一個問題的最優解,包含其子問題的最優解 2.具有重疊子問題性質 問題的遞迴演算法會反覆求解相同的子問題 複製程式碼
詳見 《什麼是動態規劃》 一章
好了,接著說回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,但是如何輸出該最長子串呢?
很簡單,只要在上述程式碼中,對其走過的字串進行標記,標記後通過反向遞迴,得到路徑,如上圖所示的灰色箭頭就是其回溯的路徑。
其他相關章節 複製程式碼