KMP算法詳解
本文的是基於我對鄧俊輝老師編著《數據結構(C++語言版)(第3版)》上關於KMP算法的理解,和網絡上一些大神們寫的博客,所寫。建議將我寫的關於implement strstr這題的博客和本篇連起來讀。
不難發現,這裏存在大量的局部匹配,針對暴力解法,若每次匹配的過程都是最後一位失配(即不匹配),文本串和模式串都得回退,並從頭開始下一輪的嘗試,這樣浪費很多時間。
1)我們可以利用以往的成功對比所提供的信息,不僅可避免文本串字符指針的回退,而且可使模式串盡可能大跨度的地右移。我們舉一個例子來得出KMP的思考過程;
本輪發現:T[i]=‘E’ !=‘O’=P[4]失配以後,在保持i不變的同時,應將模式串P右移幾個單位?觀察得出T[i-1,i) = P[0,4) = ‘REGR‘。也就是說,若比較到了T[i]說明,不管T[i]是否匹配,至少T[i]左側的若幹字符均應匹配,P[0]和T[i-1]就是這種情況。若註意到 i-1是能夠如此匹配的最左側,即可直接將P右移4-1=3個單位(等效於i保持不安,同時令 j=1),然後繼續對比。
2)如圖,若上例中,對比終止於T[i] !=P[j],按以上構想,指針 i 不必回退,而將T[i]與P[t] 對齊開始下一輪對比,那麽關鍵是:t準確應取做多少?
在上一輪的對比中可知,P[0,j) =T[i-j,i) ,若模式串P經過適當的右移移,以後,能過與T的某一(包含T[i]在內的)子串完全匹配,則必定滿足的一項必要條件是:P[0,t)=T[i-t,j)=p[j-t,j),亦即,在P[0,j)中長度為t的真前綴應與長度為t的真後綴完全匹配,故t必來自集合:
N(P,j) ={ 0<=t<j | P[0, t) = P[j-t, j) } .......(1)
一般,該集合中,可能包含多個這樣的t ,但值得註意的是:t 值的構成,僅取決於模式串P以及前一輪對比的首個失配位置P[j] ,而與文本串T無關。若下一輪從 T[i] 與 P[t] 對比開始,等效於將P右移了 j-t 個單元,與 t 成反比。為保證指針 i 絕不退後,同時又不遺失可能的匹配,應保證,選取最大的 t 值,保證,移動的距離最短。這裏很關鍵的是如何選取 t 值,即next[ ]的構造。
3)只要 j >0,即失配時,模式串P的下標大於0,則必有0屬於集合N(P ,j),也就是說,針對表達式(1)中的集合非空,最壞的情況是 t=0,即模式串又重新的從開始和文本串T[i]開始對比,相當於向右移動 j 個單位。下表的意思是,當失配的位置rank的值等於0~9時,模式串下輪對比從P的那個位置開始。如:rank=3時失配,因為,只有當 t =0 時,才滿足表達式(1),即P[0,3)中真前綴沒有和真後綴匹配的情況,則下輪比較還是要從模式串的首元素開始比較,所以next[3] =0,即向右移動3個單位;當rank=6時,P[0,2) =P[4,6) ,所以下一輪的對比中,應該是 P[2]和T[i],做對比,而對模式串P而言相當於向右移了6-2=4個單位。如果某一輪的對比中,首字符就失配,此時應該進行時,用模式串的首字符和文本串T的下一字符做比較,為統一程序和達到依舊用首字符和文本串中的下一位做比較的目的,可以令next[0]=-1,這樣,i 和 j都++以後,依舊是符合要求的。故在主程序中,i++和 j++的條件是 if(0>j || T[i]==P[j]) 。
3.1)經過3)我們得到了next[j] ,但是如何計算出next[j+1]了?
若next[j] = t ,則意味著在P[0,j)中,自匹配的真前綴和真後綴的最大長度為t ,故必有 next[j+1] <=next[j]+1 ,當且僅當P[j] = P[t]時,取等號。等號怎麽理解了?如圖:
針對模式串P中的 j 點,滿足P[0,t)=P[j-t,j),則當P[t] =P[j] 時,對模式串P的 j+1 點而言,應該是P[0,t+1) =P[j-t, j+1) ,而P[0,t+1)等於next[j]+1。至於為什麽事最大值,這點比較好想,這裏接不解釋了。
那麽一般的情況,若P[j] !=P[t]時,又該如何的得到next[j+1] ?這點可以想到,若是從next[j+1]開始比較,說明,P中j點而言都是滿足P[0,t ) =P[j-t),同時也肯定存在P[t]=P[j]。由next表的功能定義可知,next[j+1]從next[next[j]+1 ]+1,next[ next [next[j] ] ]+1..........中按照優先次序遍歷尋找P[j] =P[t] ,即,反復用next[t] 替代 t(令 t=next[t] ) ,從而可令next[j+1] = next[t]+1 。具體的程序過程參考文章開始說的博客。
KMP算法詳解