KMP算法next數組的一種理解思路


這篇博客提供一種理解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 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM