1. 程式人生 > >簡單講解KMP單模式匹配與AC演算法多模式匹配(KMP篇)

簡單講解KMP單模式匹配與AC演算法多模式匹配(KMP篇)

前言

本篇是對於KMP單模式匹配以及AC演算法多模式匹配的簡單講解,KMP演算法與AC演算法是關鍵字檢索中的常見演算法,能夠快速而高效地查找出目標字串中的多個關鍵字的匹配情況,而要檢索的關鍵字通常被稱為模式串,因此模式匹配四個字也就好理解了。網上的很多對於KMP的講解總是結合了很多的數學公式,很多的晦澀難懂的專業詞語,讓人看了很頭大,至少對於蠢笨的我來說,實在是一場煎熬,因此本篇的說明儘量做到通俗易懂,從邏輯以及思考方式的角度來對模式匹配的演算法進行講解。

一、KMP單模式匹配演算法

1、綜述

在描述多模式匹配演算法之前,對於單模式匹配先進行一個簡單的描述。單模式匹配與多模式匹配的不同點在於單模式匹配是搜尋一個關鍵字,多模式匹配是搜尋多個關鍵字。

         單模式匹配解決的是在長度為m的目標串(一個長字串L)中查詢長度為n的模式串(短字串S)的問題,如果按照最粗糙的方法暴力求解,程式碼應該是這樣的:

         for(inti=0; i<m; i++){

                   for(intj=0; j<n; j++){

                            //依次比較L[i] 與S[j],當完全相等的情況下記錄

}

}

嗯,很不錯的程式碼,思路清晰,結構明確,就是太慢了,時間代價為O(m*n)。於是情不自禁地想要快一點,這裡就請出了KMP,KMP的核心思想就是比過的字串就不比了,為了實現這一句話,KMP在模式串的基礎上,加入了next表來進行實現。

因此KMP中需要說明的就是兩點,一點是next表,另一點就是對比的方式(與暴力解法的區別在哪)。

2、next表

之前的描述中,KMP的核心在於六個大字:比過的,不比了。那麼這個實現的方式就是通過next表。這裡先舉一個簡單的模式串對應next表的例子:

j

1

2

3

4

5

6

7

pattern

a

b

c

a

b

c

d

next

0

1

1

1

2

3

4

         好的,這樣一個表格的含義是什麼呢,來猜一猜。首先是j,j的值從1-7,嗯,那就是編號?其次是pattern,pattern的意思是模式,那這個就是要檢索的模式串了?接下來是next,好像之前提到了next表,那就可以大膽預測next就代表了next表!

         那麼我猜對了嗎?恭喜我自己,答對了!(好高興)

         那好,既然每一行的名稱的意義弄清楚了,接下來就是每一行的作用,第一行的j的作用很明確,就是編號,有一點要注意的是這裡的編號的起始是1開始的而不是0。第二行的pattern是模式串,那就說明我們的模式串就是abcabcd。第三行的next代表的是next表,接下來要講的就是它。

         講next之前,先回憶一下暴力求解的時候,我們的流程是什麼,先來一個目標串(要被檢索的長字串)abcdbcaaad,我們的模式串是abcabcd,這樣首先從兩個字串的開始位置依次對比,當對比到第四個字元的時候,目標串的’d’與模式串的’a’不同,於是需要繼續對比,將模式串adbadcd向後移動一位,將模式串的’a’與目標串的第二個字元’b’又開始對比,這樣依次類推。這樣做是完全正確的,思路清晰目標明確,只是太慢了而已,而慢的原因就在於每次對比失敗時,模式串都只是向後移動一格,造成了很多重複的對比,無法實現“比過了不比了”這一偉大的巨集觀戰略目標,為了解決這個問題,經典的KMP演算法就出現了。

         那麼現在看來next表確實非常關鍵,它能夠實現我們“比過了不比了”這個偉大的目標。首先是講一講next表當中各個數字的含義和作用,例子當中的next表的值為0001230,其中每一個數字的含義是:當前字元之前的子字串片段,從前向後數與從後往前數,能夠相等的子字串的最大長度加一。額,好像依然不能令人清晰它的含義,那接下來還是用幾個例子來說明:對於字串abcab的子字串abca,從前往後數,可以得到a,ab,abc,從後往前數,可以得到a,ca,bca,明顯可以看出來,最大的相等的子字串就是a,長度為1,因此abcab的next值就應該為1+1=2,其實一眼也可以看出來,從前往後數最大也就有一個a和後面的a相等,就可以得到next值為2;再舉一個例子,abc的子字串ab,從前往後數有a,從後往前數有b,就是一個相等的都沒有,那麼就是它的next值就是0+1=1。所以到這裡應該已經懂了,就重新說一遍之前的話:當前字元之前的子字串片段,從前向後數與從後往前數,能夠相等的子字串的最大長度加一。這樣的話,next表的每一個數值的含義與計算方法就已經明確了,接下來就是它的作用。

         重複地再說一次,next表的作用是“比過了不比了”,那麼他每一個數字的作用就是記錄了實現這一目標的方法。與傳統的暴力解法不一樣,當KMP遭遇到對比不一致的情況時,KMP不是簡單地向後移動一格繼續對比,而是將向後移動(j-next[j])的距離。依然以一個例子來說明,目標字串是abadbcaaad,模式串為abcabcd,暴力解法中,對比到目標串的第三個字元時發現不一樣,於是又從目標串的第二位開始對比。這裡就是可以思考的地方了:既然目標字串的aba已經和模式串的abc進行過對比了,那我們是不是就可以不比了呢?答案是肯定的,而不比了的含義就是,可以直接將模式串abcabcd移動到目標串的第四位’d’的位置(3-next[3]=3,移動3格)開始匹配對比。那這樣突然就感覺很棒了,不再一個一個的進行對比,能夠一次性地跨越已經比過的位置,但是並不是所有的目標串都是可以直接跳過所有已經對比的子字串的,還需要考慮下面的這種情況:一個新的例子,目標串為abcabcabcd,模式串為abcabcd,首次對比中,對比到目標串的第7個字元’a’時發現了不一樣,但是這裡的移動如果直接移動到目標串第八位的位置’b’開始比較,明顯就會錯過abc(abcabcd)中括號括起來的部分,所以這裡的移動是移動到目標串的第四個字元’a’的位置(7-next[7]=3,移動3格)進行對比。

3、總結

         OK,講到這裡next表的含義和使用方法也算是講完了,而KMP演算法也就主要實現了兩點,其一是通過比過了不比了的思想直接跳過大段的字串,另一個是通過next表確認當前字元前的重複數量來避免跳過過多的字串。

         好了,今天就到這裡了,搞定收工!

(最後說明一下,在網上與書中的資料中,j與next的數值的計算方法都有些微的差異,但是根本的目的都是在於記錄j字元之前的子串的前後綴最大匹配數值,本文重在將模式匹配的邏輯與思考方式描述,不討論具體細節。)