http://blog.csdn.net/yaochunnian/article/details/7059486 ios
菜鳥一個,轉載過來跟你們一塊兒分享一下,共同進步算法
此前一天,一位MS的朋友邀我一塊兒去與他討論快速排序,紅黑樹,字典樹,B樹、後綴樹,包括KMP算法,惟獨在講解KMP算法的時候,言語磕磕碰碰,我想,緣由有二:1、博客內的東西不常回顧,忘了很多;2、即是我對KMP算法的理解還不夠完全,自不用說講解自如,運用自如了。因此,特再寫本篇文章。因爲此前,我的已經寫過關於KMP算法的兩篇文章,因此,本文名爲:KMP算法之總結篇。數組
本文分爲以下六個部分:函數
第一部分、再次回顧普通的BF算法與KMP算法各自的時間複雜度,並兩相對照各自的匹配原理;測試
第二部分、經過我此前第二篇文章的引用,用圖從頭至尾詳細闡述KMP算法中的next數組求法,並運用求得的next數組寫出KMP算法的源碼;spa
第三部分、KMP算法的兩種實現,代碼實現一是根據本人關於KMP算法的第二篇文章所寫,代碼實現二是根據本人的關於KMP算法的第一篇文章所寫;.net
第四部分、測試,分別對第三部分的兩種實現中next數組的求法進行測試,挖掘其區別之所在;指針
第五部分、KMP完整準確源碼,給出KMP算法的準確的完整源碼;orm
第六步份、一眼看出字符串的next數組各值,經過幾個例子,讓讀者能根據字符串自己一眼判斷出其next數組各值。blog
力求讓此文完全讓讀者洞穿此KMP算法,全部原理,前因後果,讓讀者搞個統統透透(注意,本文中第二部分及第三部分的代碼實現一的字符串下標i 從0開始計算,其它部分如第三部分的代碼實現二,第五部分,和第六部分的字符串下標i 皆是從1開始的)。
在看本文以前,你心中如若對前綴和後綴這個兩個概念有本身的理解,便最好了。有些東西好比此KMP算法須要咱們反覆思考,反覆求解才行。我的寫的關於KMP算法的第二篇文章爲:六(續)、從KMP算法一步一步談到BM算法;第一篇爲:6、教你初步瞭解KMP算法、updated(文末連接)。ok,如有任何問題,懇請不吝指正。多謝。
1、普通字符串匹配BF算法與KMP算法的時間複雜度比較
KMP算法是一種線性時間複雜的字符串匹配算法,它是對BF算法(Brute-Force,最基本的字符串匹配算法的)改進。對於給的原始串S和模式串P,須要從字符串S中找到字符串P出現的位置的索引。
BF算法的時間複雜度O(strlen(S) * strlen(T)),空間複雜度O(1)。
KMP算法的時間複雜度O(strlen(S) + strlen(T)),空間複雜度O(strlen(T))。
2、BF算法與KMP算法的區別
假設如今S串匹配到i位置,T串匹配到j位置。那麼總的來講,兩種算法的主要區別在於失配的狀況下,對 的值作的處理:
BF算法中,若是當前字符匹配成功,即s[i+j] == T[j],令j++,繼續匹配下一個字符;若是失配,即S[i + j] != T[j],須要讓i++,而且j= 0,即每次匹配失敗的狀況下,模式串T相對於原始串S向右移動了一位。
而KMP算法中,若是當前字符匹配成功,即S[i]==T[j],令i++,j++,繼續匹配下一個字符;若是匹配失敗,即S[i] != T[j],須要保持i不變,而且讓j = next[j],這裏next[j] <=j -1,即模式串T相對於原始串S向右移動了至少1位(移動的實際位數j - next[j] >=1),
同時移動以後,i以前的部分(即S[i-j+1 ~ i-1]),和j=next[j]以前的部分(即T[0 ~ j-2])仍然相等。顯然,相對於BF算法來講,KMP移動更多的位數,起到了一個加速的做用! (失配的特殊情形,令j=next[j]致使j==0的時候,須要將i ++,不然此時沒有移動模式串)。
三、BF算法爲何要回溯
首先說一下爲何BF算法要回溯。以下兩字符串匹配(恰如上面所述:BF算法中,若是當前字符匹配成功,即s[i+j] == T[j],令j++,繼續匹配下一個字符):
i+j(j隨T中的j++變,而動)
S:aaaacefghij
j++
T:aaac
若是不回溯的話就是從下一位開始比起:
aaaacefghij
aaac
看到上面紅顏色的沒,若是不回溯的話,那麼從a 的下一位c 比起。然而下述這種狀況就漏了(正確的作法固然是要回溯:若是失配,即S[i + j] != T[j],須要讓i++,而且j= 0):
aaaacefghij
aaac
因此,BF算法要回溯,其代碼以下:
int Index(SString S, SString T, int pos) {
//返回T在S中第pos個字符以後的位置
i=pos; j=1;k=0;
while ( i< = S[0] && j< = T[0] ) {
if (S[i+k] = = T[j] ) {++k; ++j;} //繼續比較後續字符
else {i=i+1; j=1; k=0;} //指針回溯到 下一首位,從新開始
}
if(j>T[0]) return i; //子串結束,說明匹配成功
else return 0;
}//Index
不過,也有特殊狀況能夠不回溯,以下:
abcdefghij(主串)
abcdefg(模式串)
即(模式串)沒有相同的纔不須要回溯。
4、KMP 算法思想
普通的字符串匹配算法必需要回溯。但回溯就影響了效率,回溯是由T串自己的性質決定的,是由於T串自己有先後'部分匹配'的性質。像上面所說若是主串爲abcdef這樣的,大沒有回溯的必要。
改進的地方也就是這裏,咱們從T串自己出發,事先就找準了T自身先後部分匹配的位置,那就能夠改進算法。
若是不用回溯,那模式串下一個位置從哪裏開始呢?
仍是上面那個例子,T(模式串)爲ababc,若是c失配,那就能夠往前移到aba最後一個a的位置,像這樣:
...ababd...
ababc
->ababc
這樣i不用回溯,j跳到前2個位置,繼續匹配的過程,這就是KMP算法所在。這個當T[j]失配後,j 應該往前跳的值就是j的next值,它是由T串自己固有決定的,與S串(主串)無關。
五、next數組的含義
重點來了。下面解釋一下next數組的含義,這個也是KMP算法中比較很差理解的一點。
令原始串爲: S[i],其中0<=i<=n;模式串爲: T[j],其中0<=j<=m。
假設目前匹配到以下位置
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si, Si+1,....,Sn
T0,T1,.....................,Tj-1, Tj, ..........
S和T的綠色部分匹配成功,剛好到Si和Tj的時候失配,若是要保持i不變,同時達到讓模式串T相對於原始串S右移的話,能夠更新j的值,讓Si和新的Tj進行匹配,假設新的j用next[j]表示,即讓Si和next[j]匹配,顯然新的j值要小於以前的j值,模式串纔會是右移的效果,也就是說應該有next[j] <= j -1。那新的j值也就是next[j]應該是多少呢?咱們觀察以下的匹配:
1)若是模式串右移1位(從簡單的思考起,移動一位會怎麼樣),即next[j] = j - 1, 即讓藍色的Si和Tj-1匹配(注:省略號爲未匹配部分)
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si, Si+1,....,Sn
T0,T1,.....................,Tj-1, Tj, .......... (T的劃線部分和S劃線部分相等【1】)
T0,T1,.................Tj-2,Tj-1, ....... (移動後的T的劃線部分和S的劃線部分相等【2】)
根據【1】【2】能夠知道當next[j] =j -1,即模式串右移一位的時候,有T[0 ~ j-2] == T[1 ~ j-1],而這兩部分剛好是字符串T[0 ~j-1]的前綴和後綴,也就是說next[j]的值取決於模式串T中j前面部分的前綴和後綴相等部分的長度(好好揣摩這兩個關鍵字概念:前綴、後綴,或者再想一想,個人上一篇文章,從Trie樹談到後綴樹中,後綴樹的概念)。
2)若是模式串右移2位,即next[j] = j - 2, 即讓藍色的Si和Tj-2匹配
S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1, Si, Si+1,....,Sn
T0,T1,T2,.....................,Tj-1, Tj, ..........(T的劃線部分和S劃線部分相等【3】)
T0,T1,...............,Tj-3,Tj-2,.........(移動後的T的劃線部分和S的劃線部分相等【4】)
一樣根據【3】【4】能夠知道當next[j] =j -2,即模式串右移兩位的時候,有T[0 ~ j-3] == T[2 ~ j-1]。而這兩部分也剛好是字符串T[0 ~j-1]的前綴和後綴,也就是說next[j]的值取決於模式串T中j前面部分的前綴和後綴相等部分的長度。
3)依次類推,能夠獲得以下結論:當發生失配的狀況下,j的新值next[j]取決於模式串中T[0 ~ j-1]中前綴和後綴相等部分的長度, 而且next[j]剛好等於這個最大長度。
爲此,請再容許我引用上文中的一段原文:「KMP算法中,若是當前字符匹配成功,即S[i]==T[j],令i++,j++,繼續匹配下一個字符;若是匹配失敗,即S[i] != T[j],須要保持i不變,而且讓j = next[j],這裏next[j] <=j -1,即模式串T相對於原始串S向右移動了至少1位(移動的實際位數j - next[j] >=1),
同時移動以後,i以前的部分(即S[i-j+1 ~ i-1]),和j=next[j]以前的部分(即T[0 ~ j-2])仍然相等。顯然,相對於BF算法來講,KMP移動更多的位數,起到了一個加速的做用! (失配的特殊情形,令j=next[j]致使j==0的時候,須要將i ++,不然此時沒有移動模式串)。」
於此,也就不難理解了個人關於KMP算法的第二篇文章之中:「當匹配到S[i] != P[j]的時候有 S[i-j…i-1] = P[0…j-1]. 若是下面用j_next去匹配,則有P[0…j_next-1] = S[i-j_next…i-1] = P[j-j_next…j-1]。此過程以下圖3-1所示。
當匹配到S[i] != P[j]時,S[i-j…i-1] = P[0…j-1]:
S: 0 … i-j … i-1 i …
P: 0 … j-1 j …
若是下面用j_next去匹配,則有P[0…j_next-1] = S[i-j_next…i-1] = P[j-j_next…j-1]。
因此在P中有以下匹配關係(得到這個匹配關係的意義是用來求next數組):
P: 0 … j-j_next .…j-1_ …
P: 0 … .j_next-1 …
因此,根據上面兩個步驟,推出下一匹配位置j_next:
S: 0 … i-j … i-j_next … i-1 i …
P: 0 … j_next-1 j_next …
圖3-1 求j-next(最大的值)的三個步驟
下面,咱們用變量k來表明求得的j_next的最大值,即k表示這S[i]、P[j]不匹配時P中下一個用來匹配的位置,使得P[0…k-1] = P[j-k…j-1],而咱們要儘可能找到這個k的最大值。」。
根據上文的【1】與【2】的匹配狀況,可得第二篇文章之中所謂的k=1(如aaaa的形式),根據上文的【3】與【4】的匹配狀況,k=2(如abab的形式)。
因此,歸根究底,KMP算法的本質即是:針對待匹配的模式串的特色,判斷它是否有重複的字符,從而找到它的前綴與後綴,進而求出相應的Next數組,最終根據Next數組而進行KMP匹配。接下來,進入本文的第二部分。
本部分引自我的此前的關於KMP算法的第二篇文章:六之續、由KMP算法談到BM算法。前面,咱們已經知道即不能讓P[j]=P[next[j]]成立成立。不能再出現上面那樣的狀況啊!即不能有這種狀況出現:P[3]=b,而竟也有P[next[3]]=P[1]=b。
正如在第二篇文章中,所提到的那樣:「這裏讀者理解可能有困難的是由於文中,時而next,時而nextval,把他們的思惟搞混亂了。其實next用於表達數組索引,而nextval專用於表達next數組索引下的具體各值,區別細微。至於文中說不容許P=P[next[j] ]出現,是由於已經有P
=b與S
匹配敗,而P[next
]=P1=b,若再拿P[1]=b去與S
匹配則必敗。」--六之續、由KMP算法談到BM算法。
又偏偏如上文中所述:「模式串T相對於原始串S向右移動了至少1位(移動的實際位數j - next[j] >=1)」。
ok,求next數組的get_nextval函數正確代碼以下:
//代碼4-1
//修正後的求next數組各值的函數代碼
void get_nextval(char const* ptrn, int plen, int* nextval)
{
int i = 0;
nextval[i] = -1;
int j = -1;
while( i < plen-1 )
{
if( j == -1 || ptrn[i] == ptrn[j] ) //循環的if部分
{
++i;
++j;
//修正的地方就發生下面這4行
if( ptrn[i] != ptrn[j] ) //++i,++j以後,再次判斷ptrn[i]與ptrn[j]的關係
nextval[i] = j; //以前的錯誤解法就在於整個判斷只有這一句。
else
nextval[i] = nextval[j];
}
else //循環的else部分
j = nextval[j];
}
}
舉個例子,舉例說明下上述求next數組的方法。
S a b a b a b c
P a b a b c
S[4] != P[4]
那麼下一個和S[4]匹配的位置是k=2(也即P[next[4]])。此處的k=2也再次佐證了上文第3節開頭處關於爲了找到下一個匹配的位置時k的求法。上面的主串與模式串開頭4個字符都是「abab」,因此,匹配失效後下一個匹配的位置直接跳兩步繼續進行匹配。
S a b a b a b c
P a b a b c
匹配成功P的next數組值分別爲-1 0 -1 0 2
next數組各值怎麼求出來的呢?分如下五步:
初始化:i=0,j=-1,nextval[0] = -1。因爲j == -1,進入上述循環的if部分,++i得i=1,++j得j=0,且ptrn[i] != ptrn[j](即a!=b)),因此獲得第二個next值即nextval[1] = 0;;
i=1,j=0,進入循環esle部分,j=nextval[j]=nextval[0]=-1;
進入循環的if部分,++i,++j,i=2,j=0,由於ptrn[i]=ptrn[j]=a,因此nextval[2]=nextval[0]=-1;
i=2, j=0, 因爲ptrn[i]=ptrn[j],再次進入循環if部分,因此++i=3,++j=1,由於ptrn[i]=ptrn[j]=b,因此nextval[3]=nextval[1]=0;
i=3,j=1,因爲ptrn[i]=ptrn[j]=b,因此++i=4,++j=2,退出循環。
這樣上例中模式串的next數組各值最終應該爲:
圖4-1 正確的next數組各值
next數組求解的具體過程以下:
初始化:nextval[0] = -1,咱們獲得第一個next值即-1.
圖4-2 初始化第一個next值即-1
i = 0,j = -1,因爲j == -1,進入上述循環的if部分,++i得i=1,++j得j=0,且ptrn[i] != ptrn[j](即a!=b)),因此獲得第二個next值即nextval[1] = 0;
圖4-3 第二個next值0
上面咱們已經獲得,i= 1,j = 0,因爲不知足條件j == -1 || ptrn[i] == ptrn[j],因此進入循環的esle部分,得j = nextval[j] = -1;此時,仍知足循環條件,因爲i = 1,j = -1,由於j == -1,再次進入循環的if部分,++i得i=2,++j得j=0,因爲ptrn[i] == ptrn[j](即ptrn[2]=ptrn[0],也就是說第1個元素和第三個元素都是a),因此進入循環if部份內嵌的else部分,獲得nextval[2] = nextval[0] = -1;
圖4-4 第三個next數組元素值-1
i = 2,j = 0,因爲ptrn[i] == ptrn[j],進入if部分,++i得i=3,++j得j=1,因此ptrn[i] == ptrn[j](ptrn[3]==ptrn[1],也就是說第2個元素和第4個元素都是b),因此進入循環if部份內嵌的else部分,獲得nextval[3] = nextval[1] = 0;
圖4-5 第四個數組元素值0
若是你仍是沒有弄懂上述過程是怎麼一回事,請如今拿出一張紙和一支筆出來,一步一步的畫下上述過程。相信我,把圖畫出來了以後,你必定能明白它的。
而後,我留一個問題給讀者,爲何上述的next數組要那麼求?有什麼原理麼?
提示:咱們從上述字符串abab 各字符的next值-1 0 -1 0,能夠看出來,根據求得的next數組值,偷用前綴、後綴的概念,必定能夠判斷出在abab之中,前綴和後綴相同,即都是ab,反過來,若是一個字符串的前綴和後綴相同,那麼根據前綴和後綴依次求得的next各值也是相同的。
五、利用求得的next數組各值運用Kmp算法
Ok,next數組各值已經求得,萬事俱備,東風也不欠了。接下來,我們就要應用求得的next值,應用KMP算法來匹配字符串了。還記得KMP算法是怎麼一回事嗎?容我再次引用下以前的KMP算法的代碼,以下:
//代碼5-1
//int kmp_seach(char const*, int, char const*, int, int const*, int pos) KMP模式匹配函數
//輸入:src, slen主串
//輸入:patn, plen模式串
//輸入:nextval KMP算法中的next函數值數組
int kmp_search(char const* src, int slen, char const* patn, int plen, int const* nextval, int pos)
{
int i = pos;
int j = 0;
while ( i < slen && j < plen )
{
if( j == -1 || src[i] == patn[j] )
{
++i;
++j;
}
else
{
j = nextval[j];
//當匹配失敗的時候直接用p[j_next]與s[i]比較,
//下面闡述怎麼求這個值,即匹配失效後下一次匹配的位置
}
}
if( j >= plen )
return i-plen;
else
return -1;
}
咱們上面已經求得的next值,以下:
圖5-1 求得的正確的next數組元素各值
如下是匹配過程,分三步:
第一步:主串和模式串以下,S[3]與P[3]匹配失敗。
圖5-2 第一步,S[3]與P[3]匹配失敗
第二步:S[3]保持不變,P的下一個匹配位置是P[next[3]],而next[3]=0,因此P[next[3]]=P[0],即P[0]與S[3]匹配。在P[0]與S[3]處匹配失敗。
圖5-3 第二步,在P[0]與S[3]處匹配失敗
第三步:與上文中第3小節末的狀況一致。因爲上述第三步中,P[0]與S[3]仍是不匹配。此時i=3,j=nextval[0]=-1,因爲知足條件j==-1,因此進入循環的if部分,++i=4,++j=0,即主串指針下移一個位置,從P[0]與S[4]處開始匹配。最後j==plen,跳出循環,輸出結果i-plen=4(即字串第一次出現的位置),匹配成功,算法結束。
圖5-4 第三步,匹配成功,算法結束
因此,綜上,總結上述三步爲:
開始匹配,直到P[3]!=S[3],匹配失敗;
nextval[3]=0,因此P[0]繼續與S[3]匹配,再次匹配失敗;
nextval[0]=-1,知足循環if部分條件j==-1,因此,++i,++j,主串指針下移一個位置,從P[0]與S[4]處開始匹配,最後j==plen,跳出循環,輸出結果i-plen=4,算法結束。
代碼實現一:
根據上文中第二部份內容的解析,完整寫出KMP算法的代碼已經不是難事了,以下:
//copyright@2011 binghu and july
#include "StdAfx.h"
#include <string>
#include <iostream>
using namespace std;
//代碼4-1
//修正後的求next數組各值的函數代碼
void get_nextval(char const* ptrn, int plen, int* nextval)
{
int i = 0; //注,此處與下文的代碼實現二不一樣的是,i是從0開始的(代碼實現二i從1開始)
nextval[i] = -1;
int j = -1;
while( i < plen-1 )
{
if( j == -1 || ptrn[i] == ptrn[j] ) //循環的if部分
{
++i;
++j;
//修正的地方就發生下面這4行
if( ptrn[i] != ptrn[j] ) //++i,++j以後,再次判斷ptrn[i]與ptrn[j]的關係
nextval[i] = j; //以前的錯誤解法就在於整個判斷只有這一句。
else
nextval[i] = nextval[j];
}
else //循環的else部分
j = nextval[j];
}
}
void print_progress(char const* src, int src_index, char const* pstr, int pstr_index)
{
cout<<src_index<<"\t"<<src<<endl;
cout<<pstr_index<<"\t";
for( int i = 0; i < src_index-pstr_index; ++i )
cout<<" ";
cout<<pstr<<endl;
cout<<endl;
}
//代碼5-1
//int kmp_seach(char const*, int, char const*, int, int const*, int pos) KMP模式匹配函數
//輸入:src, slen主串
//輸入:patn, plen模式串
//輸入:nextval KMP算法中的next函數值數組
int kmp_search(char const* src, int slen, char const* patn, int plen, int const* nextval, int pos)
{
int i = pos;
int j = 0;
while ( i < slen && j < plen )
{
if( j == -1 || src[i] == patn[j] )
{
++i;
++j;
}
else
{
j = nextval[j];
//當匹配失敗的時候直接用p[j_next]與s[i]比較,
//下面闡述怎麼求這個值,即匹配失效後下一次匹配的位置
}
}
if( j >= plen )
return i-plen;
else
return -1;
}
int main()
{
std::string src = "aabcabcebafabcabceabcaefabcacdabcab";
std::string prn = "abac";
int* nextval = new int[prn.size()];
//int* next = new int[prn.size()];
get_nextval(prn.data(), prn.size(), nextval);
//get_next(prn.data(), prn.size(), next);
for( int i = 0; i < prn.size(); ++i )
cout<<nextval[i]<<"\t";
cout<<endl;
cout<<"result sub str: "<<src.substr( kmp_search(src.data(), src.size(), prn.data(), prn.size(), nextval, 0) )<<endl;
system("pause");
delete[] nextval;
return 0;
}
運行結果,以下圖所示:
代碼實現二:
再給出代碼實現二以前,讓咱們再次回顧下關於KMP算法的第一篇文章中的部份內容:
「第二節、KMP算法
2.一、 覆蓋函數(overlay_function)
覆蓋函數所表徵的是pattern自己的性質,可讓爲其表徵的是pattern從左開始的全部連續子串的自我覆蓋程度。好比以下的字串,abaabcaba
可能上面的圖令讀者理解起來仍是不那麼清晰易懂,其實很簡單,針對字符串abaabcaba
a(-1) b(-1)a(0) a(0) b(1) c(-1) a(0) b(1)a(2)
解釋:
初始化爲-1
b與a不一樣爲-1
與第一個字符a相同爲0
仍是a爲0
後綴ab與前綴ab兩個字符相同爲1
前面並沒有前綴c爲-1
與第一個字符同爲0
後綴ab前綴ab爲1
前綴aba後綴aba爲2
因爲計數是從0始的,所以覆蓋函數的值爲0說明有1個匹配,對於從0仍是歷來開始計數是偏好問題,具體請自行調整,其中-1表示沒有覆蓋,那麼何爲覆蓋呢,下面比較數學的來看一下定義,好比對於序列
a0a1...aj-1 aj
要找到一個k,使它知足
a0a1...ak-1ak=aj-kaj-k+1...aj-1aj
而沒有更大的k知足這個條件,就是說要找到儘量大k,使pattern前k字符與後k字符相匹配,k要儘量的大,緣由是若是有比較大的k存在。
但若咱們選擇較小的知足條件的k,那麼當失配時,咱們就會使pattern向右移動的位置變大,而較少的移動位置是存在匹配的,這樣咱們就會把可能匹配的結果丟失。好比下面的序列,
在紅色部分失配,正確的結果是k=1的狀況,把pattern右移4位,若是選擇k=0,右移5位則會產生錯誤。計算這個overlay函數的方法能夠採用遞推,能夠想象若是對於pattern的前j個字符,若是覆蓋函數值爲k
a0a1...ak-1ak=aj-kaj-k+1...aj-1aj
則對於pattern的前j+1序列字符,則有以下可能
⑴ pattern[k+1]==pattern[j+1] 此時overlay(j+1)=k+1=overlay(j)+1
⑵ pattern[k+1]≠pattern[j+1] 此時只能在pattern前k+1個子符組所的子串中找到相應的overlay函數,h=overlay(k),若是此時pattern[h+1]==pattern[j+1],則overlay(j+1)=h+1不然重複(2)過程.
下面給出一段計算覆蓋函數的代碼:
//copyright@ staurman
//updated@2011 July
#include "StdAfx.h"
#include<iostream>
#include<string>
using namespace std;
//solve to the next array
void compute_overlay(const string& pattern)
{
const int pattern_length = pattern.size();
int *overlay_function = new int[pattern_length];
int index;
overlay_function[0] = -1;
for(int i=1;i<pattern_length;++i)
//注,與上文代碼段一不一樣的是,此處i是從1開始的,因此,下文中運用倆種方法求出來的next數組各值會有所不一樣
{
index = overlay_function[i-1];
//store previous fail position k to index;
while(index>=0 && pattern[i]!=pattern[index+1])
{
index = overlay_function[index];
}
if(pattern[i]==pattern[index+1])
{
overlay_function[i] = index + 1;
}
else
{
overlay_function[i] = -1;
}
}
for(int i=0;i<pattern_length;++i)
{
cout<<overlay_function[i]<<endl;
}
delete[] overlay_function;
}
//abaabcaba
int main()
{
string pattern = "abaabcaba";
compute_overlay(pattern);
system("pause");
return 0;
}
運行結果以下所示:
2.二、kmp算法
有了覆蓋函數,那麼實現kmp算法就是很簡單的了,咱們的原則仍是從左向右匹配,可是當失配發生時,咱們不用把target_index向回移動,target_index前面已經匹配過的部分在pattern自身就能體現出來,只要動pattern_index就能夠了。
當發生在j長度失配時,只要把pattern向右移動j-overlay(j)長度就能夠了。
若是失配時pattern_index==0,至關於pattern第一個字符就不匹配,這時就應該把target_index加1,向右移動1位就能夠了。
ok,下圖就是KMP算法的過程(紅色便是採用KMP算法的執行過程):
(另外一做者saturnman發現,在上述KMP匹配過程圖中,index=8和index=11處畫錯了。還有,anaven也早已發現,index=3處也畫錯了。很是感謝。但圖已沒法修改,見諒)
KMP 算法可在O(n+m)時間內完成所有的串的模式匹配工做。」
OK,下面此前寫的關於KMP算法的第一篇文章中的源碼:
//copyright@ saturnman
//updated@ 2011 July
#include "stdafx.h"
#include<iostream>
#include<string>
#include <vector>
using namespace std;
int kmp_find(const string& target,const string& pattern)
{
const int target_length=target.size();
const int pattern_length=pattern.size();
int* overlay_value=new int[pattern_length];
overlay_value[0]=-1; //remember:next array's first number was -1.
int index=0;
//next array
for (int i=1;i<pattern_length;++i)
//注,此處的i是從1開始的
{
index=overlay_value[i-1];
while (index>=0 && pattern[index+1]!=pattern[i]) //remember:!=
{
index=overlay_value[index];
}
if(pattern[index+1] == pattern[i])
{
overlay_value[i]=index+1;
}
else
{
overlay_value[i]=-1;
}
}
//mach algorithm start
int pattern_index=0;
int target_index=0;
while (pattern_index<pattern_length && target_index<target_length)
{
if (target[target_index] == pattern[pattern_index])
{
++target_index;
++pattern_index;
}
else if(pattern_index==0)
{
++target_index;
}
else
{
pattern_index=overlay_value[pattern_index-1]+1;
}
}
if (pattern_index==pattern_length)
{
return target_index-pattern_index;
}
else
{
return -1;
}
delete [] overlay_value;
}
int main()
{
string sourc="ababc";
string pattern="abc";
cout<<kmp_find(sourc,pattern)<<endl;
system("pause");
return 0;
}
因爲是abc跟ababc匹配,那麼將返回匹配的位置「2」,運行結果如所示:
針對上文中第三部分的兩段代碼測試了下,糾結了,兩種求next數組的方法對同一個字符串求next數組各值,獲得的結果居然不同,以下二圖所示:
一、兩種方法對字符串abab求next數組各值比較:
二、兩種對字符串abaabcaba求next數組各值比較:
爲什麼會這樣呢,其實很簡單,上文中已經有所說明了,代碼實現一的i 是從0開始的,代碼實現二的i 是從1開始的。但從最終的運行結果來看,暫時仍是以代碼實現段二爲準。
求next數組各值的方法爲:
//copyright@ staurman
//updated@2011 July
#include "StdAfx.h"
#include<iostream>
#include<string>
using namespace std;
//solve to the next array
void compute_overlay(const string& pattern)
{
const int pattern_length = pattern.size();
int *overlay_function = new int[pattern_length];
int index;
overlay_function[0] = -1;
for(int i=1;i<pattern_length;++i)
{
index = overlay_function[i-1];
//store previous fail position k to index;
while(index>=0 && pattern[i]!=pattern[index+1])
{
index = overlay_function[index];
}
if(pattern[i]==pattern[index+1])
{
overlay_function[i] = index + 1;
}
else
{
overlay_function[i] = -1;
}
}
for(int i=0;i<pattern_length;++i)
{
cout<<overlay_function[i]<<endl;
}
delete[] overlay_function;
}
//abaabcaba
int main()
{
string pattern = "abaabcaba";
compute_overlay(pattern);
system("pause");
return 0;
}
運行結果入下圖所示:abab的next數組各值是-1,-1,0,1,而非本文第二部分所述的-1,0,-1,0。爲何呢?難道是搬石頭砸了本身的腳?
NO,上文第四部分末已經詳細說明,上處代碼i 從0開始,本文第二部分代碼i 從1開始。
KMP算法完整源碼,以下:
//copyright@ saturnman
//updated@ 2011 July
#include "stdafx.h"
#include<iostream>
#include<string>
#include <vector>
using namespace std;
int kmp_find(const string& target,const string& pattern)
{
const int target_length=target.size();
const int pattern_length=pattern.size();
int* overlay_value=new int[pattern_length];
overlay_value[0]=-1; //remember:next array's first number was -1.
int index=0;
//next array
for (int i=1;i<pattern_length;++i)
//注,此處的i是從1開始的
{
index=overlay_value[i-1];
while (index>=0 && pattern[index+1]!=pattern[i])
{
index=overlay_value[index];
}
if(pattern[index+1] == pattern[i])
{
overlay_value[i]=index+1;
}
else
{
overlay_value[i]=-1;
}
}
//mach algorithm start
int pattern_index=0;
int target_index=0;
while (pattern_index<pattern_length && target_index<target_length)
{
if (target[target_index] == pattern[pattern_index])
{
++target_index;
++pattern_index;
}
else if(pattern_index==0)
{
++target_index;
}
else
{
pattern_index=overlay_value[pattern_index-1]+1;
}
}
if (pattern_index==pattern_length)
{
return target_index-pattern_index;
}
else
{
return -1;
}
delete [] overlay_value;
}
int main()
{
string sourc="ababc";
string pattern="abc";
cout<<kmp_find(sourc,pattern)<<endl;
system("pause");
return 0;
}
運行結果以下:
上文已經用程序求出了一個字符串的next數組各值,接下來,稍稍演示下,如何一眼大體判斷出next數組各值,以及初步判斷某個程序求出的next數組各值是否是正確的。有一點務必注意:下文中的代碼所有采起代碼實現二,即i是從1開始的。
一、對字符串aba求next數組各值,各位能夠先猜猜,-1,...,aba中,a初始化爲-1,第二個字符b與a不一樣也爲-1,最後一個字符和第一個字符都是a,因此,我猜其next數組各值應該是-1,-1,0,結果也不出所料,以下圖所示:
二、字符串「abab」呢,不用猜了,我已經看出來了,固然上文中代碼實現一和代碼實現二都已經求出來了。若是i 是1開始的話,那麼next數組各值將如代碼實現二所運行的那樣,將是:-1,-1,0,1;
三、字符串「abaabcaba」呢,next數組如上第三部分代碼實現二所述,爲-1,-1,0,0,1,-1,0,1,2;
四、字符串「abcdab」呢,next數組各值將是-1,-1,-1,-1,0,1;
五、字符串「abcdabc」呢,next數組各值將是-1,-1,-1,-1,0,1,2;
六、字符串「abcdabcd」呢,那麼next數組各值將是-1,-1,-1,-1,0,1,2,3;
怎麼樣,看出規律來了沒?呵呵,能夠用上述第五部分中求next數組的方法自個多試探幾回,相信,很快,你也會跟我同樣,不用計算,一眼便能看出某個字符串的next數組各值了。如此便恭喜你,理解了next數組的求法,KMP算法也就算是真真正正不折不扣的理解了。完。