KMP演算法中關於next陣列的探究

ACM_e發表於2017-08-28

一、KMP演算法簡單介紹

KMP演算法是字串匹配演算法的一種,相對於樸素的字串匹配演算法而言,可以大大避免重複遍歷的情況。此演算法可以在O(n+m)的時間數量級上完成字串匹配操作。

二、神奇的next陣列

關於KMP演算法的原理和實現,書上或者百度一下都可以找到,我在這裡就不羅嗦那麼多了,直接切入主題(next陣列)。

我們設主串S=abcabcabca,模式串p=abcabx。

KMP第一趟匹配:

                         i=6                    

S    :   a  b  c  a  b  c  a   b  c  a

位置 :  1  2  3  4  5  6  7  8  9  10

P    :   a  b   c  a  b  x

位置 :  1  2  3  4  5  6

                           j=6                      

第一次匹配到第6個位置的時候失敗了,按照樸素的演算法,i要回溯到第2個位置,j要回溯到第1個位置重新匹配。KMP的話,主串中的i是不會回溯,模式串中的j回溯也不會回溯到第1個位置。注意這裡是關鍵,i不用回溯就可以完成整個字串的匹配。為什麼i不需要回溯呢?我們先留下這個疑問。

我們把匹配成功的前5個字元研究一下。

1位置的字首子串為:a , ab , abc , abca

5位置的字尾子串為:bcab , cab , ab , b

我們觀察發現兩組裡面都有一個ab,你能看出點什麼東西麼,好的,先不管這個。

我們就按照樸素的演算法來看,i回溯到第2第3位置都會在前5個字元中匹配失敗。

樸素匹配:

                   i=4                    

S    :  a  b  c  a  b  c  a   b  c  a

位置 : 1  2  3  4  5  6  7  8  9  10

P    :             a  b  c  a  b  x

位置 :            1  2  3  4  5  6

                   j=1 

當回溯到第4個位置的時候,成功匹配的字元為ab,然後再去判斷S串的第6個字元和P串的第3個位置。這個然後我們先不管,觀察S中和P匹配的ab,在第一趟匹配的時候S中的ab是和P中前5個字元的最後兩個匹配的,而這一次匹配則是和P中前兩個字元匹配的。能發現點什麼東西麼?

不需要讓i回溯到之前的位置重新匹配,只需要找到在P串前5個字元中第一個位置的字首子串和最後一個位置的字尾子串相等並且串長最大的那一對子串,讓j指向字首子串最後一個字元的下一個位置3,和i所指向的6進行比較。往後遇見不匹配的時候採取和這個一樣的方法。

KMP第二趟匹配:

                           i=6                    

S    :   a  b  c  a  b  c  a   b  c  a

位置 :  1  2  3  4  5  6  7  8  9  10

P    :              a  b  c  a  b  x

位置 :             1  2  3  4  5  6

                           j=3 

這個時候就需要next陣列的建立了,next[6]儲存的就是前5個字元組成的字串中的第一個位置的字首子串和最後一個位置的字尾子串相等並且串長最大的那一對子串的最後一個字元的下一個位置,也就是3,也就是和P串中第3個位置匹配。

寫到這裡,next陣列應該可以得出來了。

具體程式碼怎麼得出來的,書上面都有。。那個應該不難。

對於next陣列還有一個優化,《嚴書》上講的很清晰。

三、next陣列在ACM中的應用

直接用KMP演算法真的去匹配兩個字串其實很少見,除非字串裡的字符集範圍很小,或字元重複數量過多,用KMP可大減少時間,否則一般都是直接樸素匹配。
kmp演算法在ACM中並不大可能用來直接用,主要有用的是對它的理解和它的精華部分----求 next陣列,這個的一個用途就是確定重複子串,具體參見 poj2406,poj1961,poj2752。


void get_next(string s,int next[])

{

    int length=s.length();

    int i=0,j=-1;

    next[0]=-1;

    while(i<length)

    {

        if(j==-1||s[i]==s[j])  /*s[i]表示字尾的單個字元*/

                               /*s[j]表示字首的單個字元*/

        {

            ++i;

            ++j;

            next[i]=j;

        }

        else

        j=next[j];  /*若j值不相同,則j值回溯*/

    }

}

關於 KMP 演算法 b站的一個視訊不錯

點選開啟連結



相關文章