在計算機科學中,Knuth-Morris-Pratt字符串查找算法(簡稱爲KMP算法)可在一個主文本字符串S內查找一個詞W的出現位置。此算法經過運用對這個詞在不匹配時自己就包含足夠的信息來肯定下一個匹配將在哪裏開始的發現,從而避免從新檢查先前匹配的字符。這個算法是由高德納和沃恩·普拉特在1974年構思,同年詹姆斯·H·莫里斯也獨立地設計出該算法,最終由三人於1977年聯合發表。(from:wikipedia)算法
KMP搜索(Knuth–Morris–Pratt string-searching)是字符串匹配算法中較爲高效的算法,它彌補了暴力匹配算法的一些缺點,經過回溯避免了在字符串匹配時沒必要要的步驟,縮短了匹配時間,它的時間複雜度只有O(m+n),適合在有時間要求的狀況下使用,同時也是某些比賽的考點,仍是比較有用。但此方法本質上是AC自動機的一種特殊狀況,存在必定的理解難度。本文只講解如何理解和實現kmp算法,有關數學上的說明能夠參考《算法導論》字符串匹配相關章節。數組
如下爲實現代碼,可先瀏覽,以後再作分析。函數
#include <stdio.h> #include <string.h> void getnext(char *t); //計算子串的狀態轉移數組的函數 int kmp(char *s,char *t); //kmp算法的主要匹配搜索函數 int next[255]; //全局next數組更方便調用,大小根據實際狀況更改 int main(void) { int n; char s[255],t[255]; printf("母串:"); scanf("%s",s); printf("子串:"); scanf("%s",t); n=kmp(s,t); if(n==0) printf("匹配失敗\n"); else printf("在第%d位匹配成功",n); return 0; } void getnext(char *t) { int i=0,j=-1,l=strlen(t); //j初始化爲-1只是方便計算,更易於理解,無特殊含義。 next[0]=-1; //這裏若是用next[i]=j後續有可能出現死循環,故單獨賦值。 while(i<l) { if(j==-1||t[i]==t[j]) //t[i],t[j]分別表示前綴子串單個字符和後綴子串單個字符,若匹配成功則以一種累加 { //的方式繼續向後匹配,因此每次比較一個字符,能夠動手嘗試分步理解 ++i,++j; if(t[i]!=t[j]) //這裏是針對原先方法的一些優化,後續會將 next[i]=j; else next[i]=next[j]; } else j=next[j]; //字符不相同時進行回溯 } } int kmp(char *s,char *t) { int i=0,j=0; int sl=strlen(s),tl=strlen(t); getnext(t); while(i<sl&&j<tl) { if(j==-1||s[i]==t[j]) ++i,++j; else j=next[j]; //字符串失配時回溯到正確位置再次匹配 } if(j==tl) return i-tl+1; else return 0; }
如今有母串s和子串t優化
s="abcdefgab"
t="abcdex"。設計
咱們能夠看出,兩個串前五位字符分別對應相等,只在第六位失配。若是按照暴力匹配是須要依次匹配一遍。但咱們經過觀察能夠看出,子串中六個字母各不相同,s串的首字母和t串的首字母相同,那麼就意味着子串t的首字符不可能與母串2-5之間的字符匹配成功,那麼這時,暴力匹配中就有一些步驟是徹底能夠省略的,以後的字符同理可知都能直接跳過。因爲就算咱們知道了s[5]!=t[5],t[0]!=t[5],咱們也沒法肯定t[0]必定不等於s[5],因此須要保留它們兩個匹配的那一次。code
t[i]==s[i] (i=0,1,2,3,4)
t[0]!=t[j] (j=1,2,3,4)
能夠推出:t[0]!=s[j] (j=1,2,3,4)blog
經過以上的例子,咱們能夠看出kmp算法具體是根據什麼回溯的,咱們也能夠看出這樣的回溯方式比暴力匹配好在哪裏。咱們既然是拿子串去匹配母串,那麼確定是指向子串的數字的回溯,也就是說,串中每一個對應的next值與母串無關。咱們如今能夠繼續驗證字符重複的狀況,如今咱們有子串tip
t="abcabx"字符串
咱們首先須要瞭解兩個概念:「前綴」和「後綴」。「前綴」指除了最後一個字符以外,一個字符串的所有頭部組合。「後綴」指除了第一個字符以外,一個字符串的所有尾部組合。最大公共值就是「前綴」和「後綴」的最長的共有元素的長度。其次,next數組的下標j指向第n位的時候,計算的是前n-1個字符所組成的字符串的最大公共值,由於next數組描述的是字符串在第n位失配時的轉移情況,故不考慮第n位。咱們能夠發現「ab」出現了重複,故x處對應next數組的值爲2,即爲最大公共值,這也是設next[0]=-1帶來的好處,更容易理解,更形象。以後若在x處失配,咱們能夠把總體向後挪動使得挪動以後的第一個ab對應挪動以前第二個ab的位置,繼續從c開始日後匹配。
繼續思考,咱們會發現剛纔的t串中含有兩個a,兩個b,其實這時若是用首位的值去取代後續相同的字符的next值,能夠再避免以前求next數組方法某些狀況下的重複匹配的缺陷,這個缺陷在一些連續出現同一字母的串中會出現。緣由就不展開講了,能夠用以前的方法來分析串「aaaabcde」和「aaaaax」來獲得結論。最後t串的next數組以下圖,能夠嘗試本身去求。
至此,咱們就獲得了子串的轉移數組next。get
kmp搜索函數就比較簡單了,難點主要在next函數的理解上,結合next數組把子串與母串進行匹配就好了。若是匹配失敗返回0,匹配成功則返回匹配成功的位置。此外,這只是kmp最簡單的用法,能夠根據須要對他的功能進行增長,例如求最小子串,求子串在母串的哪些地方出現等。
其實還有不少其它的字符串匹配算法,例如Sunday算法等較爲優秀的字符串模式匹配算法,且有些效率比kmp要高,但理解kmp算法也能幫助咱們更好的理解其它算法。