KMP算法

KMP算法

在字符串中確定會遇到頂頂有名的KMP算法,下面讓咱們一塊兒回顧一下吧~算法

什麼是KMP?數組

KMP是由Knuth,Morris和Pratt這三位學者發明的一種算法,因此取了三位學者名字的首字母。主要應用於字符串匹配問題上。假如文本串aabaabaaf的長度爲n,模式串aabaaf的長度爲m,咱們要判斷文本串中是否包含該模式串,傳統的解決方法是在文本串中遍歷模式串,若是遇到不匹配的字符了則又要從文本串下一位再從頭遍歷。時間複雜度爲O(m*n)。而KMP的思想在於:「當出現字符串不匹配時,能夠知道一部分以前已經匹配的文本內容,避免從頭再去作匹配了。」時間複雜度爲O(m+n)。因此KMP算法極大的提升了搜索效率。code

前綴表blog

要想了解KMP算法,首先得知道什麼是前綴後綴以及最長相等先後綴。舉個栗子🌰:字符串

對於字符串aabaaf來講,它的前綴有a,aa,aab,aaba,aabaa即除了最右端字符所組成的集合,同理它的後綴有f,af,aaf,baaf,abaaf即除了最左端字符所組成的集合。在模式串aabaaf的全部字串中get

"a"的前綴和後綴都爲空集,最長相等先後綴長度爲0;
"aa"的前綴爲[a],後綴爲[a],最長相等先後綴長度爲1;
"aab"的前綴爲[a, aa],後綴爲[b, ab],最長相等先後綴長度爲0;
"aaba"的前綴爲[a, aa, aab],後綴爲[a, ba, aba],最長相等先後綴長度爲1;
"aabaa"的前綴爲[a, aa, aab, aaba],後綴爲[a, aa, baa, abaa],最長相等先後綴長度爲2;
"aabaaf"的前綴爲[a, aa, aab, aaba, aabaa],後綴爲[f, af, aaf, baaf, abaaf],最長相等先後綴長度爲0;

能夠看到這裏獲得了一個序列,也就是你們所熟悉的next[]數組。即該模式串aabaaf的next[]數組爲[0,1,0,1,2,0],這也就是前綴表。string

爲何要用到前綴表呢?class

由於前綴表主要是用來回溯的,記錄了模式串與文本串不匹配的時候,模式串應該從哪開始匹配。效率

如圖所示:
搜索

這裏面的回退跳轉靠的就是前綴表(next[]數組)。字符串在字符f發生衝突時,就回退到f前一位next[]數組中的值所指向的位置處,也就是回退到2再進行匹配。

可能不少同窗看到的next[]數組是所有統一減一操做或者是右移首位補上-1。其實本質都是同樣的,回退到應該回退的位置,只不過修改了判斷條件。

模式串aabaaf的next[]數組三種以下,以及對應的回退方式。

求解next[]數組

主要包括四個步驟:

  1. 初始化
  2. 處理先後綴不一樣的狀況
  3. 處理先後綴相同的狀況
  4. 更新next[]數組

/推薦這種/

//當前字符串衝突時回退到前一位next數組對應的值
/*
i:指向後綴末尾
j:指向前綴末尾,也表明包括i在內的以前的最長相等先後綴的長度
*/
func getNext(next []int, s string) {
	//初始化
	j := 0   
	next[0] = 0
	for i := 1; i < len(s); i++ {
		//先後綴不相同的狀況
		for j > 0 && s[i] != s[j] {
			j = next[j-1] //回退
		}
		//先後綴相同的狀況
		if s[i] == s[j] {
			j++
		}
		next[i] = j
	}
}

next[]數組所有統一減一操做的代碼以下:

//統一減一操做
func getNext(next []int, s string) {
	//初始化
	j := -1
	next[0] = -1
	for i := 1; i < len(s); i++ {
		//先後綴不相同的狀況
		for j >= 0 && s[i] != s[j+1] {
			j = next[j]
		}
		//先後綴相同的狀況
		if s[i] == s[j+1] {
			j++
		}
		next[i] = j
	}
}
相關文章
相關標籤/搜索