【菜鳥福音】KMP算法簡單理解(從嚴蔚敏老師的《數據結構》出發)

導言:本文有如下特色:算法

(1)主要討論的是嚴蔚敏老師的《數據結構》中第四章所提到的KMP算法,即帶NEXT[]輔助數組的KMP算法;數組

(2)主要針對初學者,對算法不熟悉的同窗,主要目的是但願經過本文能讓初學者快速理解KMP算法的NEXT的計算規則。數據結構


最近複習到KMP算法,發現以前雖然好不容易弄會了手動KMP計算NEXT並且自覺得「理解」了KMP算法,結果複習起來發現基本要重看。痛定思痛,決定完全弄懂這個玩意兒。spa


相信即便是初學者也能理解KMP算法的最大優點在於不須要將模式串回退匹配來提升速度。若是給出了NEXT數組,我估計絕大部分人都可以手動模擬搜索過程。遞歸


但問題就在於,怎麼計算next數組呢?數學


嚴老師的書上給出了一種對next的計算算法。算法當然是很簡單的,重點在於解釋的過程。基礎


咱們先回顧一下書上的作法。搜索

書上從一種匹配狀況出發。im

這種匹配狀況中,已知在一次匹配的過程當中有(每一個字符寫做p[])next

‘p[1]p[2]..p[k]’=‘..p[j]’(即某段字符相等匹配),而且已知p[j]的next[p[j]]的值爲p[k]。

而後再考慮p(k+1)和p(j+1)的匹配狀況。

書上的講解雖然易懂,可是將其直接推演到求解一個模式串的next值得狀況時則有些讓人摸不着頭腦:一個串和本身相比,又是錯位相比,仍是在知道某段值得狀況下"向前看"去求下一個字符的next[],這是在是很差想象。

即便拿一個具體的例子來看,好比abbab。想要模擬一下這種假設,都讓人手忙腳亂。

固然,若是算法基礎比較好的同窗也許分分鐘搞定了這個問題,可是對於我這種來講...就須要另外一個想法了!


那麼來看看個人辦法。

首先,讓咱們釐清KMP算法中next[]的意義吧。

next數組中的某個值next[k]的意義在於指出匹配失敗時下一次匹配的模式串字符的位置.

它其實是,當模式串中的p[k]與文本串(目標串)t[j]對比時,若是不能匹配,在將模式串右移的過程當中,第一個遇到"合理的"的再一次與t[y]比較的模式串上的字符的位置,即p[next[k]]。

這個地方的合理性在於,從p[next[k]]以前的全部模式串字符,與當前位置下的目標串前面的一部分是徹底相同的,即

'p[1]p[2]...p[next[k]-1]'  =  '...t[j-1]'  =  '...p[k-1]'

釐清合理性後,讓咱們來看看怎麼求next[]。


對於一個模式串p[],咱們很容易經過定義對其頭兩個字符的next進行賦值(*:若是第一個字符都不匹配,那麼只能講模式串右移;若是第二個字符不能匹配,那麼只能再與第一個字符比較),在此處仍是寫做p[1]=0,p[2]=1;。

如今,假設咱們已經對p[k]及其以前的每一個字符都求得了next[]的值(這種假設是合理的,由於咱們顯然已經求得了p[1]、p[2]的next[]),那麼咱們如今來考慮p[k+1]對應的next值,即next[k+1]=x

回顧定義,x應當是這樣一個值:它能使得,當模式串與目標串已經匹配了k個字符時,即

'p[1]p[2]...p[k-1]p[k]'='...t[j-1]t[j]'

若是p[k+1]!=t[j+1],那麼p[x]就是下一個與t[j+1]比較的字符。

根據前面提到的合理性,此時,即移動後的模式串,在p[x]的那些字符,是與目標串上相應位置上的字符相匹配的,即有:

'p[1]p[2]...p[x-1]'  =  '...t[j-1]'  =  '...p[k]'

注意,此時也就是有

'p[1]p[2]...p[x-1]'='...p[k]'

此時相等意義正是理解算法的關鍵。

由於已經說過此時求得了p[k]的next[k]=y爲已知,而這個y的意義由前面的合理性知道,有

'p[1]p[2]..p[y-1]'='...p[k-1]'

若是此時再知足:p[y]=p[k],那麼此時有:

'p[1]p[2]..p[y-1]p[y]'  =  '...p[k-1]p[k]'  =  'p[1]p[2]...p[x-1]'

也就是說此時知足了以下條件y=x-1!(由KMP的「最長部分匹配」條件能夠得此時的推導是充要的,本文重在理解,此處的推理也就不證實啦)。那麼咱們就求得了

next[k+1]=x=y+1=next[k]+1

這就是嚴老師教材所給出的公式,不過嚴老師教材中的推理比如從後向前看,這裏更多的是從前向後看。

不過這個過程還沒結束,若是p[y]!=p[k]怎麼辦?,注意,此時咱們有

'p[1]p[2]..p[y-1]'='...p[k-1]'

因此p[y]的next值即next[y]一樣知足前面說到的合理性,所以能夠有y2=next[y],繼續遞歸...直到,最後一個yn=1。


整個過程就比如一個解方程的過程,有方程特徵(即next[]的性質和KMP的性質),有初值和邊界條件(即p[k]及以前的字符的next[]值已知,且當第一個字符不匹配時模式串右移),那麼就能夠求得P[K+1]的next[]值。


這個理解就寫到這裏啦,有許多地方數學的表達不嚴謹,重在理解嘛。

若有勘誤,請指正。歡迎討論!


謝謝閱讀!