在字符串中確定會遇到頂頂有名的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[]數組
主要包括四個步驟:
/推薦這種/
//當前字符串衝突時回退到前一位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 } }