【算法從入門到放棄】深入理解學習 最長公共子序列

前端組件框架裏面,很常見的dom處理就是將一個dom樹切換爲另外一個dom樹,現代的mvvm框架基本都是在dom樹的每一層上去做diff的。簡化的來看就是要以最小的開銷從 "abcde" 切換到 "ace"。

首先,我們瞭解一下基本概念:

1:子序列(subsequence): 一個特定序列的子序列就是將給定序列中零個或多個元素去掉後得到的結果(不改變元素間相對次序)。例如序列<A,B,C,B,D,A,B>的子序列有:<A,B>、<B,C,A>、<A,B,C,D,A>等。

2:公共子序列(common subsequence): 給定序列X和Y,序列Z是X的子序列,也是Y的子序列,則Z是X和Y的公共子序列。例如X=[A,B,C,B,D,A,B],Y=[B,D,C,A,B,A[,那麼序列Z=[B,C,A]爲X和Y的公共子序列,其長度爲3。但Z不是X和Y的最長公共子序列,而序列[B,C,B,A]和[B,D,A,B]也均爲X和Y的最長公共子序列,長度爲4,而X和Y不存在長度大於等於5的公共子序列。對於序列[A,B,C]和序列[E,F,G]的公共子序列只有空序列[]。

3:最長公共子序列:給定序列X和Y,從它們的所有公共子序列中選出長度最長的那一個或幾個。

4:子串: 將一個序列從最前或最後或同時刪掉零個或幾個字符構成的新系列。區別與子序列,子序列是可以從中間摳掉字符的。cnblogs這個字符串中子序列有多少個呢?很顯然有27個,比如其中的cb,cgs等等都是其子序列。

(圖示爲子序列與子串對比)

ps:上面基本概念轉載自https://segmentfault.com/a/1190000012864957(作者:司徒正美);

好了,現在迴歸算法主題:

例如:

我們有兩個字符串,分別爲text1text2

text1 = "ace";
text2 = "abcde";

那麼如何求出他們的最長公共子序列

先用棋盤圖來演示:

求最長公共子序列的代碼:

講解:

這裏說明一下,爲什麼會初始化棋盤的第一行以及第一列。

一個特定序列的子序列就是將給定序列中零個或多個元素去掉後得到的結果(不改變元素間相對次序),所以它也可以是空字符串,空字符串比較結果爲0。

所以我們初始化棋盤的第一列以及第一行。

接着,我們遍歷完棋盤後,去做判斷;

 

假設i=0;j=0;如果text1[0]==text2[0]的話:

if(text1[i] == text2[j]){
    dp[i + 1][j + 1] = dp[i][j] + 1
}

那麼,數組中的第二行第二列爲數組第一行第一列的值+1,

即dp[1][1] = dp[0][0] + 1;

dp[i+1][j+1]=dp[i][j]+1

 

假設i=0;j=1;如果text1[0]!== text2[1]的話:

if(text1[i] !== text2[j]){
    dp[i + 1][j + 1] = Math.max(dp[i][j + 1],dp[i + 1][j]);
}

那麼,數組中的第二行第三列的值,取(數組第一行第三列的值和數組的第二行第二列的值)這兩個值中大的值,

即dp[1][2] = Math.max( dp[0][2] , dp[1][1] );

dp[i+1][j+1]=Math.max(dp[i][j+1],dp[i+1][j])

最終輸出棋盤最後一行最後一列的值,就是我們求得的值。

下面附上完整代碼:

var text1 = "ace";
var text2 = "abcde";

var longestCommonSubsequence = function(text1,text2){
	//初始化棋盤的第一行
	let dp = [(new Array(text2.length + 1)).fill(0)];
	//這時dp dp=[
	// 	[0,0,0,0,0,0]
	// ];
	for(let i=0;i<text1.length;i++){
		//初始化棋盤第一列
		dp[i+1] = [0];
		// 這時dp=[
		// 	[0,0,0,0,0,0],
		// 	[0],
		// 	[0],
		// 	[0],
		// ]
		for(let j=0;j<text2.length;j++){
			//雙遍歷這個表格
			if(text1[i]==text2[j]){
				dp[i+1][j+1]=dp[i][j]+1
			}else{
				dp[i+1][j+1]=Math.max(dp[i][j+1],dp[i+1][j])
			}
		}
	}
	// 這時候dp爲
	// 0: (6) [0, 0, 0, 0, 0, 0]
	// 1: (6) [0, 1, 1, 1, 1, 1]
	// 2: (6) [0, 1, 1, 2, 2, 2]
	// 3: (6) [0, 1, 1, 2, 2, 3]
	return dp[dp.length - 1][dp[0].length - 1];
	//返回棋盤最後一行最後一列的結果就是我們想要的最終結果
}

longestCommonSubsequence(text1,text2);
//結果爲 3

個人筆記小結,僅幫助記憶,理論性的表達還有很多提升空間。

有看到哪裏出現錯誤的麻煩路過的大牛請指出。謝謝啦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章