這篇博客提供一種理解KMP算法中求解next數組的思路,若是從頭開始學習KMP算法,請移步這篇博客閱,作者講解的十分詳細,我本人也是從他的博客開始回顧KMP算法,本篇博客也是基於這篇博客來寫的。
請閱讀到以下位置后,若是無法理解P[k] != P[j]這部分邏輯,希望可以嘗試用本篇博客的思路來理解;若是理解了,這篇博客也可以幫你以另外一種思路理解,所以本篇博客作為這篇博客的補充更為合適。
首先明確next數組作用,設T為主串,P為模式串(即為關鍵字),next數組作用為,當T[i] != P[j]后,模式串指針 j 應該回退到next[j]位置。
如果閱讀過我說的這篇博客,就已經知道為什么指針 j 不用回退到0而要回退到next[j],因為要充分利用已經匹配的字符和模式串的特征來減少指針回退,以降低時間復雜度。
求解next數組的代碼如下:
1 void getNext(int next[], string p) 2 { 3 int j = 0, k = -1; 4 next[0] = -1; 5 while (j < p.size() - 1) { 6 if (k == -1 || p[j] == p[k]) { 7 j++; 8 k++; 9 next[j] = k; 10 } else
11 k = next[k]; 12 } 13 }
其實我們可以將KMP算法理解成兩次字符串匹配的過程:
第一次發生在求next數組過程中,就是模式串P的最長前綴子串和最長后綴子串進行匹配的過程中,其中模式串是最長前綴子串,主串是最長后綴子串,模式串和主串都在同一個字符串中。
補充:前綴子串和后綴子串概念
前綴子串:從字符串頭開始的子串。例如字符串:abcdef那么它的前綴子串為:a,ab,abc,abcd,最長的前綴子串便為 abcde;
后綴子串: 從字符串尾開始的子串。還是拿字符串abcdef舉栗子它的后綴子串為:f,ef,def,最長的后綴子串便為 bcdef。
例如下圖:
假設它本身作為KMP算法的模式串,但是在求next數組中,又可以將它看為下面這樣:
也就是在"BCABCD"中匹配"ABCABC"。
第二次當然是模式串P和主串T的匹配過程,比如下圖,也就是在下圖上面的主串中尋找下面的模式串。
在將KMP算法理解成兩次匹配過程后,next[j]數組含義就是這篇博客提到的,模式串的前綴子串集和后綴子串集的最長相同元素,也就是目前在 j 位置之前最長前綴子串和最長后綴子串已經匹配字符的個數。如果肉眼去尋找這個結果是很簡單的。
例如求模式串"ABACDABABC"的next的數組,此時的最長前綴子串為"ABACDABAB"作為模式串,最長后綴子串為"BACDABABC"作為主串(換一種說法,就是在最長后綴子串中尋找最長前綴子串)。
當P[k] == P[j]時,如下圖:
已經匹配了"ABC",j + 1位置的之前最長前綴子串和最長后綴子串匹配個數就是 k + 1 = 2 + 1 = 3(k從0開始,所以需要加1),對應代碼 7~9行:
j++; k++; next[j] = k;
當P[k] != P[j]時,如下圖,作為模式串的最長前綴子串的k指針應該回退,回退到什么位置呢?
顯然根據已經求出的next數據,就可以知道k應該回退到什么地方,k應該回退到next[k]位置,next[3] = 1,也就是k重新回到1位置,因為P[0 ~ next[k] - 1]已經和P[j - next[k] ~ j - 1]已經匹配,k指針只需要回退到next[k]位置再開始匹配即可,對應求next數組的第11行代碼如下:
k = next[k];
最后處理兩種特殊情況:
當next數組下標為0時,next[0] = -1, 放在第一次匹配過程中理解就是,當P[0] != P[j]時,k因該回退到-1,重新開始匹配過程;放在第二次匹配過程中理解就是,當P[0] != T[i]時,j 回退到-1,重新開始匹配過程。
當next數組下標為1時,next[1] = 0,放在第一次匹配過程中理解就是,當P[1] != P[j]時,k因該回退到0,從頭開始開始匹配;放在第二次匹配過程中理解就是,當P[1] != T[i]時,j 回退到0,從頭開始開始匹配。
改進求next數組方法:
上邊求next數據的方法有缺陷,再這里設P為模式串,T為主串,當P[j] != T[i]時(無論是第一次還是第二次匹配過程都有,只不過第一次求next匹配過程的模式串和主串都在同一個字符串中)會存在模式串指針 j 不能一次性回退到位的情況,當出現如下圖情況時
按照第一種求next數組方法j 應該回退到1,如下圖哦所示,但是當回退到1時,還是會出現P[j] != T[i]的情況,因為A后面跟的都是B,所以應當讓 j 直接回退到next[1] = 0,更為高效。
所以改變后代碼如下,改變在9~12行,在求next數組匹配過程中,當P[k] == P[j]或者k == 1時,並且P[k + 1] == P[j + 1]時,next[j + 1] == next[k + 1]
1 void getNext2(int next[], string p) 2 { 3 int j = 0, k = -1; 4 next[0] = -1; 5 while (j < p.size() - 1) { 6 if (k == -1 || p[j] == p[k]) { 7 j++; 8 k++; 9 if (p[j] == p[k]) 10 next[j] = next[k]; 11 else
12 next[j] = k; 13 } else
14 k = next[k]; 15 } 16 }