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的時候呢?
是的,這就是程式碼的思路,那麼這時我們就知道,核心就是要求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 |