KMP算法

說(shuō)明

KMP算法看懂了覺(jué)得特別簡(jiǎn)單屉栓,思路很簡(jiǎn)單羊赵,看不懂之前趟佃,查各種資料,看的稀里糊涂昧捷,即使網(wǎng)上最簡(jiǎn)單的解釋?zhuān)廊豢吹南±锖俊?

我花了半天時(shí)間闲昭,爭(zhēng)取用最短的篇幅大致搞明白這玩意到底是啥。?

這里不扯概念靡挥,只講算法過(guò)程和代碼理解:

KMP算法求解什么類(lèi)型問(wèn)題

字符串匹配序矩。給你兩個(gè)字符串,尋找其中一個(gè)字符串是否包含另一個(gè)字符串跋破,如果包含簸淀,返回包含的起始位置。?

如下面兩個(gè)字符串:

char*str="bacbababadababacambabacaddababacasdsd";char*ptr ="ababaca";

1

2

str有兩處包含ptr?

分別在str的下標(biāo)10幔烛,26處包含ptr啃擦。

“bacbababadababacambabacaddababacasdsd”;\?

問(wèn)題類(lèi)型很簡(jiǎn)單,下面直接介紹算法

算法說(shuō)明

一般匹配字符串時(shí)饿悬,我們從目標(biāo)字符串str(假設(shè)長(zhǎng)度為n)的第一個(gè)下標(biāo)選取和ptr長(zhǎng)度(長(zhǎng)度為m)一樣的子字符串進(jìn)行比較令蛉,如果一樣,就返回開(kāi)始處的下標(biāo)值狡恬,不一樣珠叔,選取str下一個(gè)下標(biāo),同樣選取長(zhǎng)度為n的字符串進(jìn)行比較弟劲,直到str的末尾(實(shí)際比較時(shí)祷安,下標(biāo)移動(dòng)到n-m)。這樣的時(shí)間復(fù)雜度是O(n*m)兔乞。

KMP算法:可以實(shí)現(xiàn)復(fù)雜度為O(m+n)

為何簡(jiǎn)化了時(shí)間復(fù)雜度:?

充分利用了目標(biāo)字符串ptr的性質(zhì)(比如里面部分字符串的重復(fù)性汇鞭,即使不存在重復(fù)字段,在比較時(shí)庸追,實(shí)現(xiàn)最大的移動(dòng)量)霍骄。?

上面理不理解無(wú)所謂,我說(shuō)的其實(shí)也沒(méi)有深刻剖析里面的內(nèi)部原因淡溯。

考察目標(biāo)字符串ptr:?

ababaca?

這里我們要計(jì)算一個(gè)長(zhǎng)度為m的轉(zhuǎn)移函數(shù)next读整。

next數(shù)組的含義就是一個(gè)固定字符串的最長(zhǎng)前綴和最長(zhǎng)后綴相同的長(zhǎng)度。

比如:abcjkdabc咱娶,那么這個(gè)數(shù)組的最長(zhǎng)前綴和最長(zhǎng)后綴相同必然是abc米间。?

cbcbc强品,最長(zhǎng)前綴和最長(zhǎng)后綴相同是cbc。?

abcbc屈糊,最長(zhǎng)前綴和最長(zhǎng)后綴相同是不存在的的榛。

**注意最長(zhǎng)前綴:是說(shuō)以第一個(gè)字符開(kāi)始,但是不包含最后一個(gè)字符另玖。?

比如aaaa相同的最長(zhǎng)前綴和最長(zhǎng)后綴是aaa困曙。**?

對(duì)于目標(biāo)字符串ptr,ababaca谦去,長(zhǎng)度是7慷丽,所以next[0],next[1]鳄哭,next[2]要糊,next[3],next[4]妆丘,next[5]锄俄,next[6]分別計(jì)算的是?

a,ab勺拣,aba奶赠,abab,ababa药有,ababac毅戈,ababaca的相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度。由于a愤惰,ab苇经,aba,abab宦言,ababa扇单,ababac,ababaca的相同的最長(zhǎng)前綴和最長(zhǎng)后綴是“”奠旺,“”蜘澜,“a”,“ab”响疚,“aba”兼都,“”,“a”,所以next數(shù)組的值是[-1,-1,0,1,2,-1,0]稽寒,這里-1表示不存在,0表示存在長(zhǎng)度為1趟章,2表示存在長(zhǎng)度為3杏糙。這是為了和代碼相對(duì)應(yīng)慎王。

下圖中的1,2宏侍,3赖淤,4是一樣的。1-2之間的和3-4之間的也是一樣的谅河,我們發(fā)現(xiàn)A和B不一樣咱旱;之前的算法是我把下面的字符串往前移動(dòng)一個(gè)距離,重新從頭開(kāi)始比較绷耍,那必然存在很多重復(fù)的比較⊥孪蓿現(xiàn)在的做法是,我把下面的字符串往前移動(dòng)褂始,使3和2對(duì)其诸典,直接比較C和A是否一樣。

代碼解析

voidcal_next(char*str,int*next,intlen){? ? next[0] = -1;//next[0]初始化為-1崎苗,-1表示不存在相同的最大前綴和最大后綴intk = -1;//k初始化為-1for(intq =1; q <= len-1; q++)? ? {while(k > -1&&str[k +1] !=str[q])//如果下一個(gè)不同狐粱,那么k就變成next[k],注意next[k]是小于k的胆数,無(wú)論k取任何值肌蜻。{? ? ? ? ? ? k = next[k];//往前回溯}if(str[k +1] ==str[q])//如果相同,k++{? ? ? ? ? ? k = k +1;? ? ? ? }? ? ? ? next[q] = k;//這個(gè)是把算的k的值(就是相同的最大前綴和最大后綴長(zhǎng))賦給next[q]}}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

KMP

這個(gè)和next很像必尼,具體就看代碼蒋搜,其實(shí)上面已經(jīng)大概說(shuō)完了整個(gè)匹配過(guò)程。

intKMP(char*str,intslen,char*ptr,intplen){int*next =newint[plen];? ? cal_next(ptr, next, plen);//計(jì)算next數(shù)組intk = -1;for(inti =0; i < slen; i++)? ? {while(k >-1&& ptr[k +1] !=str[i])//ptr和str不匹配胰伍,且k>-1(表示ptr和str有部分匹配)k = next[k];//往前回溯if(ptr[k +1] ==str[i])? ? ? ? ? ? k = k +1;if(k == plen-1)//說(shuō)明k移動(dòng)到ptr的最末端{(lán)//cout << "在位置" << i-plen+1<< endl;//k = -1;//重新初始化齿诞,尋找下一個(gè)//i = i - plen + 1;//i定位到該位置,外層for循環(huán)i++可以繼續(xù)找下一個(gè)(這里默認(rèn)存在兩個(gè)匹配字符串可以部分重疊)骂租,感謝評(píng)論中同學(xué)指出錯(cuò)誤祷杈。returni-plen+1;//返回相應(yīng)的位置}? ? }return-1;? }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

測(cè)試

char*str="bacbababadababacambabacaddababacasdsd";char*ptr ="ababaca";inta = KMP(str,36, ptr,7);return0;

1

2

3

4

注意如果str里有多個(gè)匹配ptr的字符串,要想求出所有的滿足要求的下標(biāo)位置渗饮,在KMP算法需要稍微修改一下但汞。見(jiàn)上面注釋掉的代碼。

復(fù)雜度分析

next函數(shù)計(jì)算復(fù)雜度是(m)互站,開(kāi)始以為是O(m^2)私蕾,后來(lái)仔細(xì)想了想,cal__next里的while循環(huán)胡桃,以及外層for循環(huán)踩叭,利用均攤思想,其實(shí)是O(m),這個(gè)以后想好了再寫(xiě)上容贝。

………………………………………..分割線……………………………………..?

其實(shí)本文已經(jīng)結(jié)束自脯,后面的只是針對(duì)評(píng)論里的疑問(wèn),我嘗試著進(jìn)行解答的斤富。

進(jìn)一步說(shuō)明(2018-3-14)

看了評(píng)論膏潮,大家對(duì)cal_next(..)函數(shù)和KMP()函數(shù)里的

while(k > -1&&str[k +1] !=str[q])? ? ? ? {? ? ? ? ? ? k = next[k];? ? ? ? }

1

2

3

4

while(k >-1&& ptr[k +1] != str[i])? ? ? ? ? ? k =next[k];

1

2

這個(gè)while循環(huán)和k=next[k]很疑惑!?

確實(shí)啊满力,我開(kāi)始看這幾行代碼焕参,相當(dāng)懵逼,這寫(xiě)的啥啊油额,為啥這樣寫(xiě)叠纷;后來(lái)上機(jī)跑了一下,慢慢了解到為何這樣寫(xiě)了悔耘。這幾行代碼讲岁,可謂是對(duì)KMP算法本質(zhì)得了解非常清楚才能想到的。很牛逼衬以!?

直接看cal_next(..)函數(shù):?

首先我們看第一個(gè)while循環(huán)缓艳,它到底干了什么。

在此之前看峻,我們先回到原程序阶淘。原程序里有一個(gè)大的for()循環(huán),那這個(gè)for()循環(huán)是干嘛的互妓?

這個(gè)for循環(huán)就是計(jì)算next[0]溪窒,next[1],…next[q]…的值。

里面最后一句next[q]=k就是說(shuō)明每次循環(huán)結(jié)束冯勉,我們已經(jīng)計(jì)算了ptr的前(q+1)個(gè)字母組成的子串的“相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度”澈蚌。(這句話前面已經(jīng)解釋了!) 這個(gè)“長(zhǎng)度”就是k灼狰。

好宛瞄,到此為止,假設(shè)循環(huán)進(jìn)行到 第 q 次交胚,即已經(jīng)計(jì)算了next[q]份汗,我們是怎么計(jì)算next[q+1]呢?

比如我們已經(jīng)知道ababab蝴簇,q=4時(shí)杯活,next[4]=2(k=2,表示該字符串的前5個(gè)字母組成的子串ababa存在相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度是3熬词,所以k=2,next[4]=2旁钧。這個(gè)結(jié)果可以理解成我們自己觀察算的吸重,也可以理解成程序自己算的,這不是重點(diǎn)歪今,重點(diǎn)是程序根據(jù)目前的結(jié)果怎么算next[5]的).晤锹,那么對(duì)于字符串ababab,我們計(jì)算next[5]的時(shí)候彤委,此時(shí)q=5, k=2(上一步循環(huán)結(jié)束后的結(jié)果)。那么我們需要比較的是str[k+1]和str[q]是否相等或衡,其實(shí)就是str[1]和str[5]是否相等焦影!,為啥從k+1比較呢封断,因?yàn)樯弦淮窝h(huán)中斯辰,我們已經(jīng)保證了str[k]和str[q](注意這個(gè)q是上次循環(huán)的q)是相等的(這句話自己想想,很容易理解)坡疼,所以到本次循環(huán)彬呻,我們直接比較str[k+1]和str[q]是否相等(這個(gè)q是本次循環(huán)的q)。?

如果相等柄瑰,那么跳出while()闸氮,進(jìn)入if(),k=k+1教沾,接著next[q]=k蒲跨。即對(duì)于ababab,我們會(huì)得出next[5]=3授翻。 這是程序自己算的或悲,和我們觀察的是一樣的。?

如果不等堪唐,我們可以用”ababac“描述這種情況巡语。 不等,進(jìn)入while()里面淮菠,進(jìn)行k=next[k]男公,這句話是說(shuō),在str[k + 1] != str[q]的情況下兜材,我們往前找一個(gè)k理澎,使str[k + 1]==str[q],是往前一個(gè)一個(gè)找呢曙寡,還是有更快的找法呢糠爬? (一個(gè)一個(gè)找必然可以,即你把 k = next[k] 換成k- -也是完全能運(yùn)行的(更正:這句話不對(duì)啊举庶,把k=next[k]換成k–是不行的执隧,評(píng)論25樓舉了個(gè)反例)。但是程序給出了一種更快的找法,那就是 k = next[k]镀琉。 程序的意思是說(shuō)峦嗤,一旦str[k + 1] != str[q],即在后綴里面找不到時(shí)屋摔,我是可以直接跳過(guò)中間一段烁设,跑到前綴里面找,next[k]就是相同的最長(zhǎng)前綴和最長(zhǎng)后綴的長(zhǎng)度钓试。所以装黑,k=next[k]就變成,k=next[2]弓熏,即k=0恋谭。此時(shí)再比較str[0+1]和str[5]是否相等,不等挽鞠,則k=next[0]=-1疚颊。跳出循環(huán)。?

(這個(gè)解釋能懂不信认?)

以上就是這個(gè)cal_next()函數(shù)里的

while(k > -1&&str[k +1] !=str[q])? ? ? ? {? ? ? ? ? ? k = next[k];? ? ? ? }

1

2

3

4

最難理解的地方的一個(gè)我的理解材义,有不對(duì)的歡迎指出。

復(fù)雜度分析:

分析KMP復(fù)雜度狮杨,那就直接看KMP函數(shù)母截。

intKMP(char*str,intslen,char*ptr,intplen){int*next =newint[plen];? ? cal_next(ptr, next, plen);//計(jì)算next數(shù)組intk = -1;for(inti =0; i < slen; i++)? ? {while(k >-1&& ptr[k +1] !=str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配)k = next[k];//往前回溯if(ptr[k +1] ==str[i])? ? ? ? ? ? k = k +1;if(k == plen-1)//說(shuō)明k移動(dòng)到ptr的最末端{(lán)//cout << "在位置" << i-plen+1<< endl;//k = -1;//重新初始化橄教,尋找下一個(gè)//i = i - plen + 1;//i定位到該位置清寇,外層for循環(huán)i++可以繼續(xù)找下一個(gè)(這里默認(rèn)存在兩個(gè)匹配字符串可以部分重疊),感謝評(píng)論中同學(xué)指出錯(cuò)誤护蝶。returni-plen+1;//返回相應(yīng)的位置}? ? }return-1;? }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

這玩意真的不好解釋?zhuān)?jiǎn)單說(shuō)一下:?

從代碼解釋復(fù)雜度是一件比較難的事情华烟,我們從?

這個(gè)圖來(lái)解釋。

我們可以看到持灰,匹配串每次往前移動(dòng)盔夜,都是一大段一大段移動(dòng),假設(shè)匹配串里不存在重復(fù)的前綴和后綴堤魁,即next的值都是-1喂链,那么每次移動(dòng)其實(shí)就是一整個(gè)匹配串往前移動(dòng)m個(gè)距離。然后重新一一比較妥泉,這樣就比較m次椭微,概括為,移動(dòng)m距離盲链,比較m次蝇率,移到末尾迟杂,就是比較n次,O(n)復(fù)雜度本慕。 假設(shè)匹配串里存在重復(fù)的前綴和后綴排拷,我們移動(dòng)的距離相對(duì)小了點(diǎn),但是比較的次數(shù)也小了锅尘,整體代價(jià)也是O(n)监氢。?

所以復(fù)雜度是一個(gè)線性的復(fù)雜度。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末藤违,一起剝皮案震驚了整個(gè)濱河市忙菠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纺弊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骡男,死亡現(xiàn)場(chǎng)離奇詭異淆游,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)隔盛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)犹菱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吮炕,你說(shuō)我怎么就攤上這事腊脱。” “怎么了龙亲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵陕凹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鳄炉,道長(zhǎng)杜耙,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任拂盯,我火速辦了婚禮佑女,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谈竿。我一直安慰自己团驱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布空凸。 她就那樣靜靜地躺著嚎花,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劫恒。 梳的紋絲不亂的頭發(fā)上贩幻,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天轿腺,我揣著相機(jī)與錄音,去河邊找鬼丛楚。 笑死族壳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的趣些。 我是一名探鬼主播仿荆,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼坏平!你這毒婦竟也來(lái)了拢操?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舶替,失蹤者是張志新(化名)和其女友劉穎令境,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體顾瞪,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舔庶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陈醒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惕橙。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖钉跷,靈堂內(nèi)的尸體忽然破棺而出弥鹦,到底是詐尸還是另有隱情,我是刑警寧澤爷辙,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布彬坏,位于F島的核電站,受9級(jí)特大地震影響膝晾,放射性物質(zhì)發(fā)生泄漏苍鲜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一玷犹、第九天 我趴在偏房一處隱蔽的房頂上張望混滔。 院中可真熱鬧,春花似錦歹颓、人聲如沸坯屿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)领跛。三九已至,卻和暖如春撤奸,著一層夾襖步出監(jiān)牢的瞬間吠昭,已是汗流浹背喊括。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矢棚,地道東北人郑什。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒲肋,于是被迫代替她去往敵國(guó)和親蘑拯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 作為NOIP選手竟然是第一次接觸KMP算法兜粘,我是個(gè)辣雞申窘。。 參考文章鏈接: https://www.cnblogs...
    JesHrz閱讀 233評(píng)論 0 0
  • 數(shù)據(jù)結(jié)構(gòu)與算法--KMP算法查找子字符串 部分內(nèi)容和圖片來(lái)自這三篇文章: 這篇文章孔轴、這篇文章剃法、還有這篇他們寫(xiě)得非常...
    sunhaiyu閱讀 1,737評(píng)論 1 21
  • 數(shù)據(jù)結(jié)構(gòu) 第8講 KMP算法 講這個(gè)算法之前,我們首先了解幾個(gè)概念: 串:又稱(chēng)字符串路鹰,是由零個(gè)或多個(gè)字符組成的有限...
    rainchxy閱讀 1,300評(píng)論 0 3
  • 引言 字符串匹配一直是計(jì)算機(jī)科學(xué)領(lǐng)域研究和應(yīng)用的熱門(mén)領(lǐng)域玄窝,算法的改進(jìn)研究一直是一個(gè)十分困難的課題。作為字符串匹配中...
    潮汐行者閱讀 1,639評(píng)論 2 6
  • 文章大綱:1.KMP算法概念2.KMP算法中最核心的next[] 數(shù)組是如何生成的3.使用KMP算法 匹配字符串 ...
    檸檬烏冬面閱讀 815評(píng)論 0 3