關於兩個字符串的kmp比對算法

關於兩個字符串的kmp比對算法

假設有字符串X和Y,知足len(X)>len(Y),要比對這兩個字符串。算法

咱們知道,最樸實的方法,就是現將兩者對齊,而後依次比對對應位置的字符。若是能匹配到Y最後位置,則匹配成功;若是匹配失敗,則將Y右移一位,再從頭進行匹配。數組

設字符串X爲dababeabafdababcg;字符串Y爲ababc。指針

這種比對方法以下所示:code

起始時,兩者對其,第一個字符不匹配 :| :dababeabafdababcg :ababc字符串

右移一位,比對位置移動到Y起始位置數學

: |
:dababeabafdababcg
: ababc

連續成功4次,再次遇到不匹配string

:     |
:dababeabafdababcg
: ababc

右移一位,比對位置移動到Y起始位置原理

:  |
:dababeabafdababcg
:  ababc
:

不斷重複該過程,直到…………搜索

:               |
:dababeabafdababcg
:           ababc

Y完整匹配到X,結束。循環

毫無疑問,這種方法太笨了。時間複雜度高達O(mn)。最大的問題是,進行了大量的重複比對工做。

kmp算法正是爲了解決這一點而提出的。kmp算法的中心思想是,已經匹配的部分不須要再次去匹配,相反,應根據已匹配的部分進行多字節的挪動,加快匹配速度。

怎麼作呢?kmp給出的答案是:

已經匹配的部分,能夠說是已知的,並且是隻取決於字符串Y的,對字符串Y的挪動能夠直接挪動到下一個知足匹配的位置。

什麼叫下一個知足匹配的位置?

假設字符串X和字符串Y已經匹配的部分爲abcab,顯然:

: abcab...  : abcab...   :
:  abcab... :   abcab... :

都是不匹配的,只有以下的

:abcab...
:   abcab...

才匹配。顯然,新的匹配序列,是舊的匹配序列的一個真後綴,並且仍是字符串Y的一個前綴;而舊的匹配序列也是字符串Y的前綴。

也就是說,根據已經匹配的部分,咱們已經能夠排除大量的位置了。kmp算法正是基於這一原理實現的。

規則1
若是當前比對位置兩個字符相同,則比對位置右移1位
規則2
若是當前比對位置兩個字符不一樣,則:

若是沒有匹配到序列,則比對位置右移1位,字符串Y右移1位
若是已有匹配到序列,則移動字符串Y到下一個匹配位置

以下例,起始

:|
:dababeabafdababcg
:ababc

字符不匹配,比對位置和字符串Y都右移一位

: |
:dababeabafdababcg
: ababc

字符匹配,比對位置右移1位

:  |
:dababeabafdababcg
: ababc

連續成功匹配4次,再次遇到不匹配

:     |
:dababeabafdababcg
: ababc

字符不匹配,挪動字符串Y到下一個匹配位置

:     |
:dababeabafdababcg
:   ababc

仍舊不匹配,繼續挪動字符串Y

:     |
:dababeabafdababcg
:     ababc

仍舊不匹配,已經沒有匹配序列了,比對位置和字符串Y都右移一位

:      |
:dababeabafdababcg
:      ababc

字符匹配,比對位置右移1位

:       |
:dababeabafdababcg
:      ababc

連續成功匹配3次,再次遇到不匹配

:         |
:dababeabafdababcg
:      ababc

字符不匹配,挪動字符串Y到下一個匹配位置

:         |
:dababeabafdababcg
:        ababc

仍舊不匹配,繼續挪動字符串Y

:         |
:dababeabafdababcg
:         ababc

字符不匹配,無匹配序列,比對位置和字符串Y都右移一位

:          |
:dababeabafdababcg
:          ababc

字符不匹配,無匹配序列,比對位置和字符串Y都右移一位

:           |
:dababeabafdababcg
:           ababc

字符匹配,比對位置右移1位

:            |
:dababeabafdababcg
:           ababc

連續成功匹配5次,到達字符串Y終點,匹配成功

:               |
:dababeabafdababcg
:           ababc

因而可知,kmp算法的比對位置沒有倒車的狀況,並且同一位置比對的次數也明顯少於樸實算法,比對速率是至關快的。

kmp算法的關鍵,是根據已有匹配序列計算下一個匹配序列,而這一部分,是隻須要字符串Y就能夠實現的,由於下一匹配序列的計算和已有匹配序列以外的部分,無關。

事實上,根據已有匹配序列,徹底能夠獲得多個知足要求的下一級匹配序列(即既是已有匹配序列的真後綴,又是字符串Y的前綴),可是毫無疑問,咱們應該選擇最長的那個。

kmp算法的關鍵,也就是對根據已有匹配序列計算下一個匹配序列這一個步驟的計算了,由於咱們知道下一匹配序列必然是字符串Y的前綴,實際上只須要記錄下一匹配序列的長度便可。這也就是所謂的next數組的真面目。(其餘人介紹的next數組的值可能和我說的不一樣,但實際上都是對下一匹配長度進行數學變換的結果)

那麼關於next數組的計算,天然也有多種方法,好比很樸實的方法,我就很少說了。事實上,有個很好的方法能夠輕鬆的計算出next數組。這個方法和kmp自己比對的過程有些相似,同時也和後綴樹構樹的ukkonen算法有殊途同歸之妙。

go代碼以下:

// 計算既是s[:i]真後綴又是其真前綴的最長(連續)序列的長度。
// 既是真後綴,也是真前綴,稱之爲(同向)迴文序列。
// 長度爲零的序列是任意序列的(同向)迴文序列。
// n[i]用來表示s[:i]最長(同向)迴文序列長度。
func next(s string) []int {    // 已知當前比較位置前的字符都匹配
	n := make([]int, l)        // n[0]、n[1]都是0,從n[2]開始算起
	for i, j := 2, 0; i < l; { // i-1表示s[:i]的後綴的當前比較位置
		if s[i-1] == s[j] {    // j  表示s[:i]的前綴的當前比較位置
			n[i] = j+1         // j+1已是最大的序列長度了
			i, j = i+1, j+1    // 同時移動兩個字符串的比較位置
			continue		   // 進入下一比較位置的循環
		}                      // 字符不匹配,最長迴文不能延長
		if j == 0 {            // 沒有非空的迴文序列了
			i++                // n[i]的默認值即爲0
			continue           // 從頭開始匹配循環
		}                      // 存在非空的迴文序列
		j = n[j]               // 找到下一個匹配位置
	}
	return n
}

咱們每一次循環的時候,只進行了一個字符的比對!這是由於以前的比對已經保證前面的部分是匹配的。那麼若是這一次比對成功,只須要延長下一次匹配的長度就好了;若是比對失敗,咱們也不須要從頭開始去查找下一個匹配的起始位置,由於以前的匹配結果已經告訴了咱們下一個匹配的位置。

結果是,kmp算法構造next數組和比對的過程,都很迅速!

kmp算法完整代碼以下:

// kmp字符串搜索算法
func KMP(s, r string) int {
   l := len(r)
   // 成員n[i]表示既是s[:i]真後綴又是s前綴的最長序列的長度。
   // 真後綴指非自身的非空後綴。如不存在,則置該成員值爲0。
   n := func(s string) []int {
       n := make([]int, l)
       // 從n[2]開始算起
       for i, j := 2, 0; i < l; {
           // 已知前面的字符所有匹配
           if s[i-1] == s[j] {
               j++
               n[i] = j
               i++
               continue
           }
           if j == 0 {
               // 申請的n會所有初始化爲零
               i++
               continue
           }
           // 相似後綴指針的功用
           j = n[j]
       }
       return n
   }(r)
   // 進行搜索
   i, j := 0, 0
   for i+l < j+len(s) && j < l {
       if s[i] == r[j] {
           i, j = i+1, j+1
           continue
       }
       if j == 0 {
           i++
       } else {
           j = n[j]
       }
   }
   if j == l {
       return i - l
   }
   return -1
}

kmp算法是一個至關精巧,可是代碼卻很是簡單的算法。相比之下,雖然速度可能遜色於BM算法,可是確實優美得多。

相關文章
相關標籤/搜索