對於正常的字符串模式匹配,主串長度爲m,子串爲n,時間複雜度會到達O(m*n),而若是用KMP算法,複雜度將會減小線型時間O(m+n),這已是很是高效的匹配算法。c++
設主串爲ptr="ababaaababaa";要比較的子串爲a=「aab」;算法
KMP算法用到了next數組,而後利用next數組的值來提升匹配速度,我首先講一下next數組怎麼求,以後再講匹配方式。數組
next數組詳解ruby
首先是理解KMP算法的第一個難關是next數組每一個值的肯定。微信
定義一串字符串ui
ptr = "ababaaababaa";spa
next[i](i從1開始算)表明着,除去第i個數,在一個字符串裏面從第一個數到第(i-1)字符串前綴與後綴最長重複的個數。.net
什麼是前綴?3d
在「aba」中,前綴就是「ab」,除去最後一個字符的剩餘字符串。code
同理能夠理解後綴。除去第一個字符的後面所有的字符串。
在「aba」中,前綴是「ab」,後綴是「ba」,那麼二者最長的子串就是「a」;
在「ababa」中,前綴是「abab」,後綴是「baba」,兩者最長重複子串是「aba」;
在「abcabcdabc」中,前綴是「abcabcdab」,後綴是「bcabcdabc」,兩者最長重複的子串是「abc」;
這裏有一點要注意,前綴必需要從頭開始算,後綴要從最後一個數開始算,中間截一段相同字符串是不行的。
再回到next[i]的定義,對於字符串ptr = "ababaaababaa";
next[1] = -1,表明着除了第一個元素,以前前綴後綴最長的重複子串,這裏是空 ,即"",沒有,咱們記爲-1,表明空。(0表明1位相同,1表明兩位相同,依次累加)。
next[2] = -1,即「a」,沒有前綴與後綴,故最長重複的子串是空,值爲-1;
next[3] = -1,即「ab」,前綴是「a」,後綴是「b」,最長重複的子串「」;
next[4] = 1,即"aba",前綴是「ab」,後綴是「ba」,最長重複的子串「a」;next數組裏面就是最長重複子串字符串的個數
next[5] = 2,即"abab",前綴是「aba」,後綴是「bab」,最長重複的子串「ab」;
next[6] = 3,即"ababa",前綴是「abab」,後綴是「baba」,最長重複的子串「aba」;
next[7] = 1,即"ababaa",前綴是「ababa」,後綴是「babaa」,最長重複的子串「a」;
next[8] = 1,即"ababaaa",前綴是「ababaa」,後綴是「babaaa」,最長重複的子串「a」;
next[9] = 2,即"ababaaab",前綴是「ababaaa」,後綴是「babaaab」,最長重複的子串「ab」;
next[10] = 3,即"ababaaaba",前綴是「ababaaab」,後綴是「babaaaba」,最長重複的子串「aba」;
next[11] = 4,即"ababaaabab",前綴是「ababaaaba」,後綴是「babaaabab」,最長重複的子串「abab」;
next[12] = 5,即"ababaaababa",前綴是「ababaaabab」,後綴是「babaaaababa」,最長重複的子串「ababa」;
Next[j]已知 求next[j+1]兩步:
1 若串中字符tj =ti ,則next[i+1]=j+1 ,j爲當前最長相等先後綴長度(不是全局)
2若tj != ti 將 ti-j+1........ti做爲主串,t1......tj做爲子串,類比於失配讓j=next[j] 繼續比較,若知足1則求得next[j+1]。如abcdcd 串中每次前綴都是從a開始的,因此只要每次不斷失配後j能跳到a,則代表回跳是對的,後綴同樣。
求next數組代碼
void Getnex(string m)//對kmp數組的構造{ nex[0]=-1; int k=-1,j=0; while(j<m.size()) { if(k==-1||m[k]==m[j]) { k++;j++; nex[j]=k; }else k=nex[k]; }}
匹配方法
next數組求值 是比較麻煩的,剩下的匹配方式就很簡單了。
next數組用於子串身上,根據上面的原理,咱們可以推出子串a=「aab」的next數組的值分別爲0,1,2.
首先開始計算主串與子串的字符,設置主串用i來表示,子串用j來表示,若是ptr[i]與a[i]相等,那麼i與j就都加1:
prt[1]與a[1]相等,i++,j++:
用代碼實現就是
if( j==0 || ptr[i]==a[j]){ ++i; ++j;}
ptr[2]與a[2]不相等
此時ptr[2]!=a[2],那麼令j = next[j],此時j=2,那麼next[j] = next[2] = 1.那麼此時j就等於1.這一段判斷用代碼解釋的話就是:
if( ptr[i]!=a[j]){ j = next[j];}
加上上面的代碼進行組合:
在對兩個數組進行比對時,各自的i,j取值代碼:
while( i<ptr.length && j< a.length){ if( j==0 || ptr[i]==a[i] ) { ++i; ++j; next[i] = j; } else { j = next[j]; }}
此時將a[j]置於j此時所處的位置,即a[1]放到j=2處,由於在j=2時出現不匹配的狀況。
此時再次計算是否匹配,能夠看出來a[1]!=ptr[2],那麼j = next[j],即此時j = next[1] = 0;
根據上面的代碼,當j=0時,執行++i;++j;
此時就變爲:
此時ptr[3] = a[1],繼續向下走,下一個又不相等了,而後「aab」向後挪一位,這裏再也不贅述了,主要的思想已經講明白了。到最後一直到i = 8,j=3時匹配成功,KMP算法結束。整個過程就結束了。
代碼
bool SUBMIT = false;using namespace std;const int inf = 1000;int nex[inf];string s,h;void Getnex(string m)//對kmp數組的構造{ nex[0]=-1; int k=-1,j=0; while(j<m.size()) { if(k==-1||m[k]==m[j]) { k++;j++; nex[j]=k; }else k=nex[k]; }}int kmp()//用kmp進行匹配{ int k=0,j=0; while(j<h.size()) { if(k==-1||s[k]==h[j]) { k++;j++; }else{ k=nex[k]; cout<<k<<" "<<j<<endl; } if(k == s.size()) return j-k; } return -1;}int main(){ cin>>h>>s; cout<<h<<endl<<s<<endl; Getnex(s); for(int i=0;i<s.size();i++) cout<<nex[i]; cout<<endl; int ans=kmp(); cout<<ans<<endl; return 0;}
本文分享自微信公衆號 - WHICH工做室(which_cn)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。