搞acm也有三年了,期間學習了不少演算法,到12月把上海站打完也要成退役狗了。近期突然想把學過的一些演算法回過頭來好好總結一下。於是就有了我的演算法總結系列。這是這個系列的開端,所以先寫乙個簡單點的演算法,以後會慢慢複習一些複雜的演算法。最後還是希望自己能夠堅持下去吧。
kmp演算法是一種線性時間複雜度的字串匹配演算法,它是對bf(brute-force,最主要的字串匹配演算法)的改進。
對於給定的原始串s和模式串t,須要從字串s中找到字串t出現的位置的索引。kmp演算法由d.e.knuth與v.r.pratt和j.h.morris同一時候發現,因此人們稱它為knuth--morris--pratt演算法,簡稱kmp演算法。在解說kmp演算法之前,有必要對它的前身--bf演算法有所了解,因此首先將介紹最樸素的bf演算法。
如上圖所看到的,原始串s=abcabcabdabba,模式串為abcabd。(下標從0開始)從s[0]開始依次比較s[i] 和t[i]是否相等。直到t[5]時發現不相等,這時候說明發生了失配,在bf演算法中,發生失配時,t必須回溯到最開始,s下標+1。然後繼續匹配。例如以下圖所看到的:
這次馬上發生了失配,所以繼續回溯,直到s開始下表新增到3,匹配成功。
easy得到。bf演算法的時間複雜度是o(n*m)的,當中n為原始串的長度,m為模式串的長度。
bf的**實現也非常easy直觀,這裡不給出,由於下乙個介紹的kmp演算法是bf演算法的改進。其時間複雜度為線性o(n+m),演算法實現也不比bf演算法難多少。
前面提到了樸素匹配演算法。它的長處就是簡單明瞭,缺點當然就是時間消耗非常大,既然知道了bf演算法的不足,那麼就要對症下藥,設計一種時間消耗小的字串匹配演算法。
kmp演算法就是當中乙個經典的樣例,它的主要思想就是:
在匹配匹配過程中發生失配時。並不簡單的從原始串下乙個字元開始又一次匹配,而是依據一些匹配過程中得到的資訊跳過不必要的匹配,從而達到乙個較高的匹配效率。
還是前面的樣例,原始串s=abcabcabdabba,模式串為abcabd。當第一次匹配到t[5]!=s[5]時,kmp演算法並不將t的下表回溯到0,而是回溯到2,s下標繼續從s[5]開始匹配。直到匹配完成。
那麼為什麼kmp演算法會知道將t的下標回溯到2呢?前面提到,kmp演算法在匹配過程中將維護一些資訊來幫助跳過不必要的檢測,這個資訊就是kmp演算法的重點 --next陣列。(也叫fail陣列。字首陣列)。
(1)next陣列的定義:
設模式串t[0,m-1],(長度為m),那麼next[i]表示既是是串t[0,i-1]的字尾又是串t[0,i-1]的字首的串最長長度(最好還是叫做前字尾),注意這裡的字首和字尾不包含串t[0,i-1]本身。
如上面的樣例,t=abcabd,那麼next[5]表示既是abcab的字首又是abcab的字尾的串的最長長度,顯然應該是2,即串ab。注意到前面的樣例中,當發生失配時t回溯到下表2。和next[5]陣列是一致的,這當然不是個巧合,其實。kmp演算法就是通過next陣列來計算發生失配時模式串應該回溯到的位置。
(2)next陣列的計算:
這裡介紹一下next陣列的計算方法。
設模式串t[0,m-1],長度為m。由next陣列的定義,可知next[0]=next[1]=0,(由於這裡的串的字尾。字首不包含該串本身)。
接下來,假設我們從左到右依次計算next陣列。在某一時刻,已經得到了next[0]~next[i],如今要計算next[i+1]。設j=next[i],由於知道了next[i]。所以我們知道t[0,j-1]=t[i-j,i-1],如今比較t[j]和t[i],假設相等。由next陣列的定義,能夠直接得出next[i+1]=j+1。
假設不相等,那麼我們知道next[i+1]
1)t[0,po-1]=t[i-po,i-1]。
2)t[po]=t[i]。
3)po是滿足條件(1),(2)的最大值。
4)0<=po
怎樣求得這個po值呢?其實,並不能直接求出po值。僅僅能一步一步接近這個po,尋找當前位置j的下乙個可能位置。假設僅僅要滿足條件(1),那麼j就是乙個,那麼下乙個滿足條件(1)的位置是什麼呢?,由next陣列的定義,easy得到是next[j]=k,這時候僅僅要推斷一下t[k]是否等於t[i],就可以推斷是否滿足條件(2),假設還不相等。繼續減小到next[k]再推斷,直到找到乙個位置p,使得p同一時候滿足條件(1)和條件(2)。我們能夠得到p一定是滿足條件(1),(2)的最大值,由於假設存在乙個位置x使得滿足條件(1),(2),(4)而且x>po,那麼在回溯到p之前就能找到位置x,否則和next陣列的定義不符。在得到位置po之後,easy得到next[i+1]=po+1。那麼next[i+1]就計算完成,由數學歸納法,可知我們能夠求的全部的next[i]。(0<=i
注意:在回溯過程中可能有一種情況,就是找不到合適的po滿足上述4個條件,這說明t[0,i]的最長前字尾串長度為0,直接將next[i+1]賦值為0,就可以。
//計算串str的next陣列int getnext(char *str,int next)
{ int len=strlen(str);
next[0]=next[1]=0;//初始化
for(int i=1;i
以上是計算next陣列的**實現。
是不是非常簡短呢。
有了next陣列。我們就能夠通過next陣列跳過不必要的檢測。加快字串匹配的速度了。那麼為什麼通過next陣列能夠保證匹配不會漏掉可匹配的位置呢?
首先,假設發生失配時t的下標在i,那麼表示t[0,i-1]與原始串s[l,r]匹配,設next[i]=j,依據kmp演算法,能夠知道要將t回溯到下標j再繼續進行匹配,依據next[i]的定義。能夠得到t[0,j-1]和s[r-j+1,r]匹配。同一時候可知對於不論什麼j
同next陣列的計算,在普通情況下。可能回溯到next[i]後再次發生失配。這時僅僅要繼續回溯到next[j]。假設不行再繼續回溯。最後回溯到next[0],假設還不匹配。這時說明原始串的當前位置和t的開始位置不同。僅僅要將原始串的當前位置+1。繼續匹配就可以。
以下給出kmp演算法匹配過程的**:
//返回s串中第一次出現模式串t的開始位置
int kmp(char *s,char *t)
{ int l1=strlen(s),l2=getnext(t);//l2為t的長度,getnext函式將在以下給出
int i,j=0,ans=0;
for(i=0;i
前面說到,kmp演算法的時間複雜度是線性的,但這從**中並不easy得到。非常多讀者可能會想,假設每次匹配都要回溯非常多次。是不是會使演算法的時間複雜度退化到非線性呢?
其實不然。我們對**中的幾個變數進行討論。首先是kmp函式。顯然決定kmp函式時間複雜度的變數僅僅有兩個,i和j,當中i僅僅新增了len次,是o(len)的,以下討論j,由於由next陣列的定義我們知道next[j]
到這裡,kmp演算法的實現已經完成。可是這還不是最完整的的kmp演算法,真正的kmp演算法須要對next陣列進行進一步優化,可是如今的演算法已經達到了時間複雜度的下線,而且,如今的next陣列的定義保留了一些非常實用的性質。這在解決一些問題時是非常有幫助的。
對於優化後的kmp演算法。有興趣的朋友能夠自行查閱相關文件。
KMP演算法總結
kmp題目重在理解next陣列的含義 next陣列的作用 next j 記錄模式串中第 j 個字元的最長公共字首長度 重要,這是它的意義所在 第二種理解方式,當模式串與主串失配時,跳回的位置。next len 即字串 0 結束標誌的next值 單個字串匹配時與週期有關 hdu 1711 模板題 33...
KMP演算法總結
kmp演算法是用來實現模式匹配的,其時間複雜度是o m n 具體原理見 其中有用到next陣列來計算子串中公共項的位數,簡單來說,就是子串遇到不匹配時,就查next資料來決定前進幾位 移動位數 已匹配的字元數 對應的部分匹配值 1 要不要減一看next陣列第一位是不是為1,個人覺得加一後是有好處的,...
KMP演算法總結
現在假設有兩個字串a bbc abcdab abcdabde,b abcdabd。現在要在a中找b。比較暴力的方法是直接搜尋 void gosearch 上述 最核心就是while迴圈,舉兩個例子說明其作用 還是選取b串作為說明。假設現在要求next 6 那麼當前的j next 5 1 next陣列...