KMP演算法以及優化(程式碼分析以及求解next陣列和nextval陣列)

ZaunEkko發表於2021-05-23

KMP演算法以及優化(程式碼分析以及求解next陣列和nextval陣列)

來了,資料結構及演算法的內容來了,這才是我們的專攻,前面寫的都是開胃小菜,本篇文章,側重考研408方向,所以保證了你只要看懂了,題一定會做,難道這樣思想還會不會麼?如果只想看next陣列以及nextval陣列的求解可以直接跳到相應部分,思想總結的很乾~~

網上的next陣列版本解惑

先總結一下,一般KMP演算法的next陣列結果有兩個版本,我們需要知道為什麼會存在這種問題,其實就是字首和字尾沒有匹配的時候next陣列為0還是為1,兩個版本當然都是對的了,如果next陣列為0是的版本,那麼對於字首和字尾的最大匹配長度只需要值+1就跟next陣列是1的版本一樣了,其實是因為他們的原始碼不一樣,或者對於模式串的第一個下標理解為0或者1,總之這個問題不用糾結,懂原理就行~~

那麼此處,我們假定字首和字尾的最大匹配長度為0時,next陣列值為1的版本,考研一般都是用這個版本(如果為0版本,所有的內容-1即可,如你算出next[5]=6,那麼-1版本的next[5]就為5,反之亦然)~~

其實上面的話總結就是一句話

next[1]=0,j(模式串)陣列的第一位下標為1,同時,字首和字尾的最大匹配長度+1即為next陣列的值,j所代表的的是序號的意思

408反人類,一般陣列第一位下標為1,關於書本上前面連結串列的學習大家就應該有目共睹了,書本上好多陣列的第一位下標為了方便我們理解下標為1,想法這樣我們更不好理解了,很反人類,所以這裡給出next[1]=0,字首和字尾的最大匹配長度+1的版本講解

前言以及問題引出

我們先要知道,KMP演算法是用於字串匹配的~~

例如:一個主串"abababcdef"我們想要知道在其中是否包括一個模式串"ababc"

初代的解決方法是,樸素模式匹配演算法,也就是我們主串和模式串對比,不同主串就往前移一位,從下一位開始再和模式串對比,每次只移動一位,這樣會很慢,所以就有三位大神一起搞了個演算法,也就是我們現在所稱的KMP演算法~~

程式碼以及理解

原始碼這裡給出~~

int Index_KMP(SString S,SString T,intt next[]){
    int i = 1,j = 1;//陣列第一位下標為1
    while (i <= S.length && j <= T.length){
        if (j == 0 || S.ch[i] == T.ch[j]){//陣列第一位下標為1,0的意思為陣列第一位的前面,此時++1,則指向陣列的第一位元素
            ++i;
            ++j;	//繼續比較後繼字元
        }
        else
            j = next[j];	//模式串向右移動到第幾個下標,序號(第一位從1開始)
    }
    if (j > T.length)
        return i - T.length;	//匹配成功
    else
        return 0;
    
    
}

接下來就可以跟我來理解這個程式碼~~

還不會做動圖,這裡就手畫了~~

圖片

以上是一般情況,那麼如何理解j=next[1]=0的時候呢?

img

是的,這就是程式碼的思路,那麼這時我們就知道,核心就是要求next陣列各個的值,對吧,一般也就是考我們next陣列的值為多少~~

next陣列的求解

這裡先需要給出概念,串的字首以及串的字尾~~

串的字首:包含第一個字元,且不包含最後一個字元的子串

串的字尾:包含最後一個字元,且不包含第一個字元的子串

當第j個字元匹配失敗,由前1~j-1個字元組成的串記為S,則:next[j]=S的最長相等前字尾長度+1

與此同時,next[1]=0

如,模式串"ababaa"

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0

當第六個字串匹配失敗,那麼我們需要在前5個字元組成的串S"ababa"中找最長相等的前字尾長度為多少再+1~~

如串S的字首可以為:"a","ab","aba","abab",字首只不包括最後一位都可

串S的字尾可以為:"a","ba","aba","baba",字尾只不包括第一位都可

所以這裡最大匹配串就是"aba"長度為3,那麼我們+1,取4

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 4

再比如,當第二個字串匹配失敗,由前1個字元組成的串S"a"中,我們知道字首應當沒有,字尾應當沒有,所以最大匹配串應該為0,那麼+1就是取1~~

其實這裡我們就能知道一個規律了,next[1]一定為0(原始碼所造成),next[2]一定為1(必定沒有最大匹配串造成)~~

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 4

再再比如,第三個字串匹配失敗,由前兩個字元組成的串S"ab"中找最長相等的前字尾長度,之後再+1~~

字首:"a"

字尾:"b"

所以所以這裡最大匹配串也是沒有的長度為0,那麼我們+1,取1

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 4

接下來你可以自己練練4和5的情況~~

這裡直接給出答案~~

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4

是不是很簡單呢?

至此,next陣列的求法以及kmp程式碼的理解就ok了~~


那麼接下來,在瞭解以上之後,我們想一想KMP演算法存在的問題~~

KMP演算法存在的問題

如下

主串:"abcababaa"

模式串:"ababaa"

例如這個問題

我們很容易能求出next陣列

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4

此時我們是第三個字串匹配失敗,所以我們的next[3]=1,也就是下次就是第一個字元"a"和主串中第三個字元"c"對比,可是我們剛開始的時候就已經知道模式串的第三個字元"a"和"c"不匹配,那麼這裡不就多了一步無意義的匹配了麼?所以我們就會有kmp演算法的一個優化了~~

KMP演算法的優化

我們知道,模式串第三個字元"a"不和主串第三個字元"c"不匹配,next陣列需要我們的next[3]=1,也就是下次就是第一個字元"a"和主串中第三個字元"c"對比,之後就是模式串第一個字元"a"不和"c"匹配,就是需要變為next[1]=0,那麼我們要省去步驟,不就可以直接讓next[3]=0麼?

序號J 1 2 3 4 5
模式串 a b a b a
next[j] 0 1 1 2 3
nextval[j] 0 0

那麼怎麼省去多餘的步驟呢?

這就是nextval陣列的求法~~

nextval的求法以及程式碼理解

先貼出程式碼

for (int j = 2;j <= T.length;j++){
	if (T.ch[next[j]] == T.ch[j])
        nextval[j] = nextval[next[j]];
    else
        nextval[j] = next[j];
}

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
nextval[j] 0

首先,第一次for迴圈,j=2,當前序號b的next[2]為1,即第一個序號所指向的字元a,a!=當前序號b,所以nextval[2]保持不變等於next[2]=1

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
nextval[j] 0 1

第二次for迴圈,j=3,當前序號a的next[3]為1,即第一個序號所指向的字元a,a=當前序號a,所以nextval[3]等於nextval[1]=0

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
nextval[j] 0 1 0

第三次for迴圈,j=4,當前序號b的next[4]為2,即第二個序號所指向的字元b,b=當前序號b,所以nextval[4]等於nextval[2]=1

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
nextval[j] 0 1 0 1

就是這樣,你可以練練5和6,這裡直接給出~~

序號J 1 2 3 4 5 6
模式串 a b a b a a
next[j] 0 1 1 2 3 4
nextval[j] 0 1 0 1 0 4

至此nextval陣列的求法你也應該會了,那麼考研要是考了,那麼是不是就等於送分給你呢?


小練習

那麼你試著來求一下這個模式串的next和nextval陣列吧~~

模式串:"aaaab"

序號j 1 2 3 4 5
模式串 a a a a b
next[j]
nextval[j]

小練習的答案

序號j 1 2 3 4 5
模式串 a a a a b
next[j] 0 1 2 3 4
nextval[j] 0 0 0 0 4

相關文章