KMP算法的next[]數組通俗解釋

前言

         本文是對KMP核心部分NEXT數組的構建方法說明,KMP總體分析的文章能夠參考下面的連接。算法

http://my.oschina.net/u/572632/blog/277548編程

概述

     咱們在一個母字符串中查找一個子字符串有不少方法。KMP是一種最多見的改進算法,它能夠在匹配過程當中失配的狀況下,有效地多日後面跳幾個字符,加快匹配速度。數組

     固然咱們能夠看到這個算法針對的是子串有對稱屬性,若是有對稱屬性,那麼就須要向前查找是否有能夠再次匹配的內容。在KMP算法中有個數組,叫作前綴數組,也有的叫next數組,每個子串有一個固定的next數組,它記錄着字符串匹配過程當中失配狀況下能夠向前多跳幾個字符,固然它描述的也是子串的對稱程度,程度越高,值越大,固然以前可能出現再匹配的機會就更大。這個next數組的求法是KMP算法的關鍵,但不是很好理解,我在這裏用通俗的話解釋一下,看到別的地方處處是數學公式推導,看得都蛋疼,這個篇文章僅貢獻給不喜歡看數學公式又想理解KMP算法的同窗。函數

分析

     用一個例子來解釋,下面是一個子串的next數組的值,能夠看到這個子串的對稱程度很高,因此next值都比較大。優化


一 逐個查找對稱串

這個很簡單,咱們只要循環遍歷這個子串,分別看前1個字符,前2個字符,3個... i個 最後到15個。spa

第1個a無對稱,因此對稱程度0.net

前兩個ag無對稱,因此也是0code

依次類推前面0-4都同樣是0blog

前5個agcta,能夠看到這個串有一個a相等,因此對稱程度爲1前6個agctag,看獲得ag和ag對成,對稱程度爲2繼承

這裏要注意了,想是這樣想,編程怎麼實現呢?

只要按照下面的規則:

  1. 當前面字符的前一個字符的對稱程度爲0的時候,只要將當前字符與子串第一個字符進行比較。這個很好理解啊,前面都是0,說明都不對稱了,若是多加了一個字符,要對稱的話最可能是當前的和第一個對稱。好比agcta這個裏面t的是0,那麼後面的a的對稱程度只須要看它是否是等於第一個字符a了。

  2. 按照這個推理,咱們就能夠總結一個規律,不只前面是0呀,若是前面一個字符的next值是1,那麼咱們就把當前字符與子串第二個字符進行比較,由於前面的是1,說明前面的字符已經和第一個相等了,若是這個又與第二個相等了,說明對稱程度就是2了。有兩個字符對稱了。好比上面agctag,倒數第二個a的next是1,說明它和第一個a對稱了,接着咱們就把最後一個g與第二個g比較,又相等,天然對稱成都就累加了,就是2了。

  3. 按照上面的推理,若是一直相等,就一直累加,能夠一直推啊,推到這裏應該一點難度都沒有吧,若是你以爲有難度說明我寫的太失敗了。

固然不可能會那麼順利讓咱們一直對稱下去,若是遇到下一個不相等了,那麼說明不能繼承前面的對稱性了,這種狀況只能說明沒有那麼多對稱了,可是不能說明一點對稱性都沒有,因此遇到這種狀況就要從新來考慮,這個也是難點所在。

二 回頭來找對稱性

這裏已經不能繼承前面了,可是仍是找對稱成都嘛,最愚蠢的作法大不了寫一個子函數,查找這個字符串的最大對稱程度,怎麼寫方法不少吧,好比查找出全部的當前字符串,而後向前走,看是否一直相等,最後走到子串開頭,固然這個是最蠢的,咱們通常看到的KMP都是優化過的,由於這個串是有規律的。

在這裏依然用上面表中一段來舉個例子:   

位置i=0到14以下,我加的括號只是用來講明問題:

(a g c t a g c )( a g c t a g c) t

咱們能夠看到這段,最後這個t以前的對稱程度分別是:1,2,3,4,5,6,7,倒數第二個c往前看有7個字符對稱,因此對稱爲7。可是到最後這個t就沒有繼承前面的對稱程度next值,因此這個t的對稱性就要從新來求。

這裏首要要申明幾個事實

  1. t 若是要存在對稱性,那麼對稱程度確定比前面這個c 的對稱程度小,因此要找個更小的對稱,這個不用解釋了吧,若是大那麼t就繼承前面的對稱性了。

  2. 要找更小的對稱,必然在對稱內部還存在子對稱,並且這個t必須緊接着在子對稱以後。

代碼

從上面的理論咱們就能獲得下面的前綴next數組的求解算法。
void SetPrefix(const char *Pattern, int prefix[])
{
     int len=CharLen(Pattern);//模式字符串長度。
     prefix[0]=0;
     for(int i=1; i<len; i++)
     {
         int k=prefix[i-1];
 //不斷遞歸判斷是否存在子對稱,k=0說明再也不有子對稱,Pattern[i] != Pattern[k]說明雖然對稱,可是對稱後面的值和當前的字符值不相等,因此繼續遞推
         while( Pattern[i] != Pattern[k]  &&  k!=0 )               
             k=prefix[k-1];     //繼續遞歸
         if( Pattern[i] == Pattern[k]) 
//找到了這個子對稱,或者是直接繼承了前面的對稱性,這兩種都在前面的基礎上++
              prefix[i]=k+1;
         else
              prefix[i]=0;       //若是遍歷了全部子對稱都無效,說明這個新字符不具備對稱性,清0
     }
}

優化

KMP還有一種寫法:這個寫法是通過N我的優化的:
int  j = -1,  i = 0;
next[0] = -1;
while(i < len)
{
          if(j == -1 || ss[i] == ss[j])
         {
                    i++;
                    j++;
                    next[i] = j;
         }
         else
        {
                   j = next[j];
        }
}
相關文章
相關標籤/搜索