hihocoder 1015 : KMP算法(kmp)
傳送門
Description
小Hi和小Ho是一對(duì)好朋友,出生在信息化社會(huì)的他們對(duì)編程產(chǎn)生了莫大的興趣,他們約定好互相幫助,在編程的學(xué)習(xí)道路上一同前進(jìn)。
這一天,他們遇到了一只河蟹,于是河蟹就向小Hi和小Ho提出了那個(gè)經(jīng)典的問題:“小Hi和小Ho,你們能不能夠判斷一段文字(原串)里面是不是存在那么一些……特殊……的文字(模式串)?”
小Hi和小Ho仔細(xì)思考了一下,覺得只能想到很簡(jiǎn)單的做法,但是又覺得既然河蟹先生這么說(shuō)了,就肯定不會(huì)這么容易的讓他們回答了,于是他們只能說(shuō)道:“抱歉,河蟹先生,我們只能想到時(shí)間復(fù)雜度為(文本長(zhǎng)度 * 特殊文字總長(zhǎng)度)的方法,即對(duì)于每個(gè)模式串分開判斷,然后依次枚舉起始位置并檢查是否能夠匹配,但是這不是您想要的方法是吧?”
河蟹點(diǎn)了點(diǎn)頭,說(shuō)道:”看來(lái)你們的水平還有待提高,這樣吧,如果我說(shuō)只有一個(gè)特殊文字,你能不能做到呢?“
小Ho這時(shí)候還有點(diǎn)暈暈乎乎的,但是小Hi很快開口道:”我知道!這就是一個(gè)很經(jīng)典的模式匹配問題!可以使用KMP算法進(jìn)行求解!“
河蟹滿意的點(diǎn)了點(diǎn)頭,對(duì)小Hi說(shuō)道:”既然你知道就好辦了,你去把小Ho教會(huì),下周我有重要的任務(wù)交給你們!“
”保證完成任務(wù)!”小Hi點(diǎn)頭道。
提示一:KMP的思路
提示二:NEXT數(shù)組的使用
提示三:如何求解NEXT數(shù)組
Input
第一行一個(gè)整數(shù)N,表示測(cè)試數(shù)據(jù)組數(shù)。
接下來(lái)的N*2行,每?jī)尚斜硎疽粋€(gè)測(cè)試數(shù)據(jù)。在每一個(gè)測(cè)試數(shù)據(jù)中,第一行為模式串,由不超過(guò)10^4個(gè)大寫字母組成,第二行為原串,由不超過(guò)10^6個(gè)大寫字母組成。
其中N<=20
Output
對(duì)于每一個(gè)測(cè)試數(shù)據(jù),按照它們?cè)谳斎胫谐霈F(xiàn)的順序輸出一行Ans,表示模式串在原串中出現(xiàn)的次數(shù)。
Sample Input
5 HA HAHAHA WQN WQN ADA ADADADA BABABB BABABABABABABABABB DAD ADDAADAADDAAADAADSample Output
3 1 3 1 0?
#include <bits/stdc++.h> using namespace std; const int maxn = 1000005; int nxt[maxn]; char a[maxn], b[maxn];void getNxt(char *pattern, int len){nxt[0] = -1;int i = 0, k = -1;while (i < len){if (k == -1 || pattern[k] == pattern[i]){k++;i++;//優(yōu)化nxt數(shù)組 if (pattern[k] != pattern[i]) nxt[i] = k;else nxt[i] = nxt[k];}else{k = nxt[k];}} }int kmp(char *origin, int len1, char *pattern, int len2){int i = 0, k = 0, res = 0;while (i < len1){if (k == -1 || pattern[k] == origin[i]) {i++;k++;}else k = nxt[k];if (k == len2){res++, k = nxt[k];} }return res; } int main(){int N;scanf("%d", &N);while (N--){memset(nxt, 0, sizeof(nxt));scanf("%s %s", a, b);getNxt(a, strlen(a));printf("%d\n", kmp(b, strlen(b), a, strlen(a)));} return 0; }?
提示
提示一:
小Hi和小Ho回到了學(xué)校,為了完成河蟹托付的偉大使命,小Hi立馬把小Ho抓到了機(jī)房開始上課。
“小Ho,你來(lái)看看這樣一段原串和模式串~”小Hi說(shuō)著遞上了一張紙條。
| 原串: | bababababababababb |
| 模式串: | bababb |
“嗯,這個(gè)例子中模式串bababb在原串中第13個(gè)字符開始的地方出現(xiàn)了”小Ho看了看,回答道。
“我們假設(shè)仍然使用最普通的方法來(lái)進(jìn)行判斷,即我們先枚舉原串中的一個(gè)起始位置,然后判斷從這個(gè)位置開始的字符串是否能和模式串進(jìn)行完匹配。”小HI說(shuō)道,“然后我們來(lái)看這個(gè)過(guò)程中有沒有什么可以縮減的計(jì)算量。”
“好的!”小Ho點(diǎn)點(diǎn)頭。
“你看,在起始點(diǎn)為1的時(shí)候,匹配到第6個(gè)字符的時(shí)候發(fā)生了失敗,這個(gè)時(shí)候我們應(yīng)當(dāng)做的是是不是將模式串右移一位,然后從頭開始判斷,就像這樣?”小Hi又在紙上畫了畫,遞給了小Ho。
| 原串: | bababababababababb |
| 模式串: | bababb |
| 原串: | bababababababababb |
| 模式串: | ??bababb |
”是的,然后我們發(fā)現(xiàn)第一位就發(fā)現(xiàn)不能進(jìn)行匹配。“小Ho老老實(shí)實(shí)的回答。
”然后我們?cè)賹⒛J酱乙埔晃?#xff0c;然后再?gòu)念^開始判斷,這次我們成功的越過(guò)了原串的第7個(gè)字符,在第8個(gè)字符產(chǎn)生了不同。“小Hi繼續(xù)往下推演。
| 原串: | bababababababababb |
| 模式串: | ????bababb |
”然后之后的劇情非常的相似,都是要么最后一個(gè)字符匹配不成功,要么就是第一個(gè)字符就匹配不成功,一直到了最后一次機(jī)會(huì)的時(shí)候才匹配成功。“小Ho做了總結(jié)。
”那你覺得這個(gè)過(guò)程中有沒有什么沒有必要計(jì)算的呢?“小Hi于是問道。
”我是這么認(rèn)為的,你看這條線。“小Ho在兩個(gè)串上對(duì)著的一個(gè)位置畫了一條線。
| 原串: | babab |?ababababababb |
| 模式串: | babab |?b |
”嗯?”
“這是我們第一次產(chǎn)生了字符不匹配的情況,那么接下來(lái)的過(guò)程中一定會(huì)出現(xiàn)兩種情況之一:一種情況是模式串與原串的對(duì)齊點(diǎn)(即枚舉的原串中的起點(diǎn)位置)越過(guò)了這條線,仍然沒能匹配成功,而另一種情況是原串中這個(gè)位置的字符與模式串中某個(gè)位置的字符匹配上了。”小Ho分析道:”我們先不考慮第一種情況,而來(lái)看看第二種情況會(huì)發(fā)生什么。“
| 原串: | babab |?ababababababb |
| 模式串(對(duì)齊點(diǎn)=1): | babab |?b |
| 模式串(對(duì)齊點(diǎn)=3): | ????bab | a |
”看不出嘛,小Ho你今天變成聰明了嘛!~”小Hi由衷的贊嘆道。
“那當(dāng)然,畢竟我最近在討論區(qū)解答了很多問題,這很鍛煉人的好么!“小Ho笑嘻嘻的回答道。
”那我也得表現(xiàn)下,接下來(lái)?yè)Q我來(lái)說(shuō)吧,反正你肯定也就差不多想到這么多是吧!“小Hi也是看破了小Ho的底細(xì),這般說(shuō)道。于是小Ho點(diǎn)了點(diǎn)頭,讓小Hi接著說(shuō)。
”我相信一個(gè)很容易注意到的事實(shí)就在于,如果我用i表示原串和模式串產(chǎn)生分歧的位置(模式串上的位置,注意!這個(gè)和對(duì)齊點(diǎn)是不一樣的東西,一個(gè)在原串上,一個(gè)在模式串上),用j表示為了匹配掉位置i上產(chǎn)生分歧的字符而將模式串的對(duì)齊點(diǎn)移動(dòng)到的位置,我們會(huì)發(fā)現(xiàn),模式串[1, i-j]的這一段和[j, i - 1]這一段是相同的。比如在這個(gè)例子中i=6,j=3,我們會(huì)發(fā)現(xiàn)模式串[1, 3]和[3,5]是相同的。“小Hi整理了下思路,如是說(shuō)道。
| 原串: | ba | bab |?a?babababababb |
| 模式串(i=1): | ba | bab |?b |
| 模式串(i=3): | ???? | bab | a |
”而我們同時(shí)也會(huì)發(fā)現(xiàn),只有在存在一個(gè)長(zhǎng)度k,使得模式串[1, i-k]和[k, i-1]這兩段相同的情況下,將模式串對(duì)其到位置k,才能保證原串和模式串的匹配過(guò)程能夠進(jìn)入到原串的位置i是否和模式串的對(duì)應(yīng)字符相同的判定,在別的情況下,根本都進(jìn)入不到位置i的判斷就會(huì)發(fā)生不一致的情況了。”說(shuō)著小Hi又拋出了另外一個(gè)命題。
“我已經(jīng)開始有點(diǎn)暈了!”小Ho提出了抗議。
“那你就好好的讀一遍我剛才說(shuō)的話!然后自己在草稿紙上演算一下這個(gè)樣例,很快就可以得出結(jié)果的!”小Hi如是說(shuō)道。”總而言之我們現(xiàn)在需要的一個(gè)數(shù)據(jù)是,這個(gè)長(zhǎng)度k最長(zhǎng)是多少,而且我們對(duì)于模式串的每一個(gè)位置i,都要計(jì)算這個(gè)值。”而這就是KMP中最為重要的一個(gè)點(diǎn)——NEXT數(shù)組。
提示二:
“那么,為了能夠充分理解NEXT數(shù)組,我們?cè)賮?lái)回顧一下如何使用NEXT數(shù)組~"小Hi擺出一副老師的樣子,說(shuō)道。”首先我們來(lái)給出NEXT數(shù)組的數(shù)學(xué)定義~“
NEXT[0] = -1 NEXT[i] = max{ 0<=k< i | str.substring(1, k) == str.substring(i - k +1 , i) } 其中str.substring(i, j)表示str從位置i到位置j的子串,如果i>j則,substring為空”那么我們對(duì)之前例子中的模式串進(jìn)行求解,可以得到這樣的NEXT數(shù)組。“小Hi在紙上寫了又寫,畫了又畫。
| 模式串: | b a b a b b |
| NEXT: | 0 0 1 2 3 1 |
”然后再來(lái)看這個(gè)NEXT數(shù)組是如何使用的!為了表明NEXT的所有使用情況,我們換一個(gè)原串。然后首先,我們第一次匹配,如果用ori表示原串,用par表示模式串,用p表示原串的下標(biāo)(從1開始),用q表示模式串的下標(biāo)(從1開始)的話,會(huì)發(fā)現(xiàn)最多匹配到p=5, q=5就不能往下匹配了,因?yàn)榇藭r(shí)ori[p +1]不等于par[q + 1]“小Hi為了使說(shuō)明更加簡(jiǎn)潔,先下了一堆定義。
”好的!小Hi老師好棒!“小Ho在一旁煽風(fēng)點(diǎn)火道。
| 原串(p=5): | babab |?abcbababababb |
| 模式串(q=5): | babab |?b |
”此時(shí),令q = NEXT[q],并將ori[1..p]和par[1..q]對(duì)齊,便會(huì)發(fā)現(xiàn)ori[1..p]和par[1..q]仍然是一一對(duì)應(yīng)的。“
| 原串(p=5): | babab | abcbababababb |
| 模式串(q=3): | ????bab | abb |
“此時(shí),ori[p+1]和par[q+1]相同了,于是可以繼續(xù)往下匹配,但是到了p=7,q=5的時(shí)候又發(fā)現(xiàn)不能夠接著匹配了。”
| 原串(p=7): | bababab |?cbababababb |
| 模式串(q=5): | ????babab |?b |
”此時(shí),令q = NEXT[q],并將ori[1..p]和par[1..q]對(duì)齊,便會(huì)發(fā)現(xiàn)ori[1..p]和par[1..q]仍然是一一對(duì)應(yīng)的,這和之前是一樣的。”
| 原串(p=7): | bababab |?cbababababb |
| 模式串(q=3): | ????????bab |?abb |
“此時(shí),ori[p+1]和par[q+1]仍然不相同,于是還得令q=NEXT[q]。”
| 原串(p=7): | bababab |?cbababababb |
| 模式串(q=1): | ????????????b |?ababb |
“此時(shí),ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。”
| 原串(p=7): | bababab |?cbababababb |
| 模式串(q=0): | ?????????????? |?bababb |
“此時(shí),ori[p+1]和par[q+1]仍然不相同,令q=NEXT[q]。”
| 原串(p=7): | bababab |?cbababababb |
| 模式串(q=-1): | ?????????????? | ??bababb |
”到了這一步,就相當(dāng)于我們之前所說(shuō)的模式串與原串的對(duì)齊點(diǎn)(即枚舉的原串中的起點(diǎn)位置)越過(guò)了這條線(當(dāng)時(shí)指C右側(cè)的那條線)的情況,這種情況下,就應(yīng)當(dāng)p和q均+1,然后繼續(xù)之前的操作。”小Hi擦了一把汗,說(shuō)道。
“這樣一說(shuō),我就大致能夠理解NEXT數(shù)組是怎么用來(lái)求解模式匹配問題的了,但是它是如何求的呢?一般的方法不是要O(模式串長(zhǎng)度的立方)的么?”小Ho問道。
“這就是我接下來(lái)要和你說(shuō)的啦!”小Hi笑道:“但是讓我先喝口水!”
提示三:
“首先我們不想如何求整個(gè)NEXT數(shù)組,而是假設(shè)我們已經(jīng)知道了之前例子中模式串的NEXT[1..4],來(lái)求NEXT[5]如何?”小Hi建議道。
“好的!這樣我們就只需要平方級(jí)的算法就可以算出它的值了!”小Ho高興道。
“有點(diǎn)追求好不好!”小Hi深深的吸了一口氣:“你這樣和之前的解法有什么不同么!”
“似乎沒有。。那你說(shuō)怎么算吧!我反正腦子已經(jīng)成漿糊了。”小Ho郁悶道。
“我們把par.substring(1, 5)當(dāng)做新的原串ori_new,然后把par.substring(1, 4)當(dāng)做新的模式串par,會(huì)如何?”小Hi微微一笑。
“會(huì)。。我來(lái)試試!"小Ho接過(guò)小Hi手中的紙筆,便開始演算:“首先就直接匹配到了p=4, q=4的情況,這時(shí)候嚴(yán)格來(lái)說(shuō)已經(jīng)算匹配完成了,但是肯定不是就這么結(jié)束的,此時(shí)par_new[q +1]因?yàn)槭强兆址?#xff0c;所以肯定和ori_new[p+1]匹配不上。于是令q = NEXT[q]”
| 原串(p=4): | baba |?b |
| 模式串(q=4): | baba | |
| 原串(p=4): | baba |?b |
| 模式串(q=2): | ????ba |?b |
”然后這時(shí)候ori_new[p + 1]就直接和par_new[q + 1]匹配上了,于是新的p=5,q=3,莫非……這個(gè)最后的q就是NEXT[5]!“小Ho忽然靈光一閃。
”沒錯(cuò),就是這樣!那你想想現(xiàn)在如何求NEXT[6]。“小Hi繼續(xù)引導(dǎo)小Ho。
”首先我們沒有必要重新從頭開始匹配,直接在原串和模式串的后面加上第6個(gè)字符就可以了。“小Ho分析道。
| 原串(p=5): | babab |?b |
| 模式串(q=3): | ????bab |?abb |
”沒法繼續(xù)匹配,于是令q=NEXT[q]。“
| 原串(p=5): | babab |?b |
| 模式串(q=1): | ????????b |?ababb |
”還是沒法繼續(xù)匹配,于是令q=NEXT[q]。“
| 原串(p=5): | babab |?b |
| 模式串(q=0): | ?????????? |?bababb |
”此時(shí)可以匹配了,新的p=6,q=1,所以NEXT[6]就是1!“小Ho高興道:”沒想到NEXT數(shù)組的本身會(huì)用一種遞歸的方式進(jìn)行求解,真是太巧妙了!“
”那你要不要趕緊去寫一下代碼,KMP算法的代碼可是可以寫的很短很巧妙的哦!~“小Hi建議道。
”好!“
轉(zhuǎn)載于:https://www.cnblogs.com/ZhaoxiCheung/p/6846739.html
總結(jié)
以上是生活随笔為你收集整理的hihocoder 1015 : KMP算法(kmp)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android NDK 环境搭建
- 下一篇: 第十二周学习进度