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