字符串匹配——KMP算法

對於正常的字符串模式匹配,主串長度爲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算法結束。整個過程就結束了。

代碼

#include<bits/stdc++.h>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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索