算法 - KMP算法原理顿悟有感
算法 - KMP算法原理頓悟有感
- KMP?
- KMP核心思想
- 舉個栗子
- 上點代碼
- next數組
- (1)若P~j~ == P~t~
- (2) 若P~j~ 和 P~t~不相等
- 改進上面的KMP算法
- nextval
- 求解nextval數組的一般方法
- 上代碼
中國開國七十一年三月十二日下午,解衣欲睡,雨聲入耳,悟以往,思來日,懼無以為生計,遂至嗶哩嗶哩尋KMP算術,觀后遂頓悟,以作文記之。
?
KMP?
? KMP算法是在某一字符串中尋找給定子串的算法,比起逐一比較的方法,KMP算法快了不少。
?
KMP核心思想
? 比較過程如圖所示:
? 此處的疑點:為什么有時候模式串一次往后移那么多?為什么有時候模式串又只往后移一格?到底是怎么個模式串移動法?
? 這就是KMP的關鍵。
? 答案:當發現模式串的第i個字符匹配錯誤時,將模式串向后移動N個位置。此處,N = 模式串第i個字符前的子串中的長度 - 模式串第i個字符前的子串最長且小于子串本身長度的公共前后綴的長度。
一句話總結:讓i之前的公共前綴移動到公共后綴的位置上。
由此可知,KMP算法的移動主要和模式串有關,和待查找的字符串關系不大。
?
舉個栗子
假設模式串為 ABABAAABABAA
假設模式串與帶比較字符串的第1位比較不匹配:則模式串前移1位(1號位與主串下一位比較)
假設模式串與帶比較字符串的第2位比較不匹配:二號位前的子串為A,公共前后綴長度是0,則模式串向前移1位(1號位與主串當前位比較)
假設模式串與帶比較字符串的第3位比較不匹配:三號位前的子串為AB,公共前后綴長度是0,則模式串向前移1位(1號位與主串當前位比較)
假設模式串與帶比較字符串的第4位比較不匹配:三號位前的子串為ABA,公共前后綴長度是1,則模式串向前移2位(2號位與主串當前位比較)
到這為之,如圖所示:
我們得到一個規律,如果當前子串最大公共前后綴長度為N,那我們就移動模式串使N+1號位與主串當前位比較。
那么以此類推:
我們把第一句話標記為0,當我們看到0時,將1號位與主串下一位比較。
并且我們將后面的每一句話的第一個數字取出,結合上面的數組下標,將這些數字放在一個數組中,這樣一來,根據數組所提供的信息,我們在模式串上任何一個位置匹配失敗,就知道下一步該怎么做了:
這個數組就是傳說中的next數組。
?
上點代碼
? 循序漸進,若想知道KMP算法的代碼實現怎么寫,我們先要知道天真的傻瓜式比較法的代碼怎么寫。
//too simple, sometimes naive //假設字符串位置從1開始 int naive(String str, String substr) {int i = 1, j = 1, k = i; //i:主串游標,j:模式串游標,k:位置記錄器while (i<=str.length && j<=substr.length){ //i、j都落在各自字符串的范圍內if(str.ch[i] == substr.ch[j]){i++;j++;}else {j = 1;i = ++k; //尋找初始比較位置的下一個位置}}if (j>substr.length) return k;else return -1; }現在用KMP的思想做一點修改:
int KMP(String str, String substr, int next[]) {int i = 1, j = 1; //KMP不需要回溯,不需要kwhile (i<=str.length && j<=substr.length){ if(j == 0 || str.ch[i] == substr.ch[j]){ //此處注意j=0的處理i++;j++;}else {j = next[j]; //i不需要回溯,j有next數組指導往哪走 }}if (j>substr.length) return i-substr.length; //計算首字符存在的位置else return -1; }而關鍵在于:next數組怎么獲得?
?
next數組
KMP所做的事的聰明之處在于:把之前工作的結果合理利用起來,減少重復勞動。
求解next數組需要繼承這一思想。
假設一段模式串如下圖所示:
P為模式串中的字符,P的下標代表每一個字符的位置,模式串的長度為m
現在我們把模式串復制一份,并凸顯出1到t位置上的字符,也就是左端長度為t的子串
我們將上面Pj-t+1到Pj的子串與下面P1到Pt的子串對應起來,假設紅色部分完全匹配,黃色部分暫時不知道,則next[j] = t。
現在我們需要求next[j+1]的值。
?
(1)若Pj == Pt
那么很容易求得next[j] = t + 1 = next[j] + 1
?
(2) 若Pj 和 Pt不相等
當Pj 不等于 Pt時,這時的情況似曾相識:主串某個位置與模式串某個位置發生不匹配的現象。
假如我們把上面的字符串稱為假主串,下面的稱為假模式串:
這不就是熟悉的KMP嗎!
在我們已知next[j]求next[j+1]的情況下時,我們可以使用next[j]之前所有的next數組
所以當若Pj 和 Pt不相等時,則循環將t賦值為next[t],直到t=0或者滿足(1)為止,當t = 0時,next[j+1] = 1。
總結:
我們發現,這種求法天生適合翻譯成代碼:
void getNext(string substr, int next[]) {int t = 0, j = 1;next[1] = 0;while (j<substr.length){if(t == 0 || substr.ch[j] == substr.ch[t]){next[j+1] = t + 1;t++; j++;}else t = next[t];} }? 在求完next數組之后,為了理解接下來的工作,以及不忘初心,我們必須回顧及總結next數組的含義到底是什么。
? next[5] = 3意味著5這個位置之前的子串的公共前后綴長度為2。
? 一句話:next[i] = j意味著i這個位置之前的模式串有j-1個字符是能夠與主串匹配的。
?
改進上面的KMP算法
? 這里就讓人頭疼了,本來上面那個就不怎么好理解,還要改進?
在求解next[j]時,上圖其實做了很多重復的工作。
因為1到4上的字符串相等,因此next[5]直接賦值為0即可。
所以針對KMP算法的改進主要集中在求解next數組的改進上,我們把改進之后的數組稱為nextval數組。
?
nextval
? nextval的優化思路:求解nextval[j]時,移過來比較的字符必須與比較過不符合要求的Pj不相同。
? 如上圖所示:其中P代表模式串中的字符,我們若要求nextval[j],需不停將j賦值為next[j],再把這些位置上的字符與Pj進行比較,如果它們與Pj相等,那么意味著Pj與主串中的字符未匹配,則這些位置上的字符來到這也沒用。若其中有一個字符與Pj不等,那nextval[j]則為對應位置(上圖Pa中的)。
?
求解nextval數組的一般方法
- 若Pj不等于Pnext[j],則nextval[j]等于next[j]
- 若Pj等于Pnext[j],則nextval[j]等于nextval[next[j]]
?
上代碼
void getNextval(string substr, int nextval[]) {int t = 0, j = 1;nextval[1] = 0; //這句很明顯要加上while (j<substr.length){if(t == 0 || substr.ch[j] == substr.ch[t]){//計算nextval值if(substr.ch[j+1] != substr.ch[t+1]])nextval[j+1] = t + 1;elsenextval[j+1] = nextval[t + 1];t++; j++;}else t = nextval[t]; //用nextval代替next數組} }總結
以上是生活随笔為你收集整理的算法 - KMP算法原理顿悟有感的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022摩根士丹利笔试
- 下一篇: SAP-PS-如何解决项目Q库存Pr不占