日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

KMP--字符串匹配

發(fā)布時間:2025/3/16 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 KMP--字符串匹配 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

原創(chuàng):http://blog.csdn.net/jiacongxu/article/details/9071491

以下內(nèi)容參考了這個文章:http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm

這兩天重新看KMP,發(fā)現(xiàn)問題還蠻多的。以前知道KMP怎么用,復(fù)雜度如何,但是寫起來總是做不到bug-free。把這兩天重新看的,寫的關(guān)于KMP的東西,拿出來總結(jié)一下。

首先介紹下KMP:KMP的全名是Knuth-Morris-Pratt algorithm,好吧,其實就是三個作者的名字加起來。它是用來做什么的呢?字符串匹配。下面是摘下來的一段介紹:

“The algorithm of Knuth, Morris and Pratt?[KMP?77]?makes use of the information gained by previous symbol comparisons. It never re-compares a text symbol that has matched a pattern symbol. As a result, the complexity of the searching phase of the Knuth-Morris-Pratt algorithm is in?O(n).

However, a preprocessing of the pattern is necessary in order to analyze its structure. The preprocessing phase has a complexity ofO(m). Since?mn, the overall complexity of the Knuth-Morris-Pratt algorithm is in?O(n)”

?對于暴力的做法,我們枚舉母串的所有字符作為匹配的開始位置,對模式串進行匹配,假設(shè)母串長度n,模式串長度m,時間復(fù)雜度是O(nm)的。在這個暴力的過程中,我們每次匹配都“從零開始”,忽略了前面的匹配結(jié)果給我們帶來的信息,KMP算法巧妙的利用了前面的匹配結(jié)果,最大限度的避免重復(fù)的匹配,使得算法在最壞情況復(fù)雜度為O(n+m)。下面具體講講KMP算法。

KMP的第一步是利用O(m)時間,對模式串進行一個預(yù)處理。這一步完全是對于模式串的,跟母串沒有關(guān)系。前面提到,暴力做法慢的原因在于,當我已經(jīng)知道模式串的前k個字符與母串匹配而第k+1個字符不匹配時,我們在母串中選用下一個字符作為匹配開始節(jié)點,對模式串從頭開始匹配。但是在前面一次失敗匹配中,我們已經(jīng)知道有k個字符可以匹配,如果這k個字符符合某些特定條件,我們是不是不需要重新開始匹配,而只需要把模式串的匹配位置往前回滾幾個字符呢?答案當然是肯定的,下面先給出一個例子說明:

在上面的例子中,'d'出現(xiàn)了不匹配,而前面五個字符都是匹配的,按照暴力的做法,現(xiàn)在應(yīng)該把模式串往后推一個字符,重新開始匹配。KMP算法注意到,對于模式串"abcabd",現(xiàn)在'd'跟母串'5'的位置出現(xiàn)了不匹配,而“abcab“是匹配成功的,那我們完全可以把模式串往后”推“不止一個字符,我們把它往后推三個字符,就像例子描述的,這樣,母串'5'的位置跟模式串的'c'對在一起了,而前面的"ab"仍然是匹配成功的,也就是說,用暴力做法,模式串應(yīng)該往后移動一個字符,重新匹配,但KMP缺移動了多個字符,而且也不需要重頭匹配了。


為什么我們發(fā)現(xiàn)可以移動三個字符呢?因為對于匹配成功的串”abcab“最長的”前綴等于后綴“的串是”ab“(這里不把字符串本身當做自己的前后綴,不然最長的”前綴等于后綴“一定是串本身)。我假設(shè)大家都知道什么是前綴和后綴了(如果不知道,google一下就有了)。想象一下,既然對于現(xiàn)在成功匹配的串長度是len1,如果我知道最長的”前綴等于后綴“的長度為len2,那么我就可以保證把模式串往后推len1-len2個字符之后,模式串還是可以成功匹配到母串的當前位置的(在這個例子中,len1是5,len2是2,母串的當前位置是5)。為什么能保證到?因為前綴等于后綴。把模式串往后推了在匹配,就相當于拿前綴跟后綴匹配,所以,你懂的。

基本上kmp就是這個思路,我們知道當前匹配成功的字符串長度,又知道這個匹配成功的字符串的最長的”前綴等于后綴“的長度,我們就可以知道,我們應(yīng)該把模式串后移多少個字符去匹配。要找到這個最長的”前綴等于后綴“的長度,KMP也告訴你了,可以用O(m)的復(fù)雜度來實現(xiàn),一般來說,把這個結(jié)果存在一個叫next的數(shù)組里面。代碼如下:

[cpp]?view plaincopy
  • vector<int>?GetNext(char*?p){??
  • ????????vector<int>?next;??
  • ????????next.push_back(-1);??
  • ????????for(int?i=0;p[i];i++){?//求next[i+1]??
  • ????????????int?tmp?=?next[i];???
  • ????????????while(tmp!=-1&&p[i]!=p[tmp])?//找出一個最長的”前綴等于后綴“的長度??
  • ??????????????tmp?=?next[tmp];?????
  • ????????????next.push_back(tmp+1);//<span?style="font-family:?Arial,?Helvetica,?sans-serif;?font-size:?12px;">如果p[i]==p[tmp],說明最長的長度是tmp+1</span>??
  • ????????}??
  • ????????return?next;??
  • ????}??

  • 為了更直觀的看看next存了什么,用上面給的example,當模式串是”abcabd“時,對應(yīng)的next為:


    模式串:?a ? ?b ? ?c ? ?a ? ?b ? ?d

    next: ? ? ?? -1 ? 0 ? ?0 ? ?0 ? ?1 ? ?2 ? ?0 ? ?


    next指針告訴了我們,當模式串的第i個位置出現(xiàn)mismatch時,模式串應(yīng)該跳回到那個位置(i=next[i])。也就是說,模式串后移了i-next[i]位。next[i]保存的是串p[0:i-1]最長的”前綴等于后綴“的長度。next[0] = -1可以很好的作為一個標志位,說明沒有前后綴符合要求,簡化編程難度。


    拿到next數(shù)組之后,我們就需要利用next數(shù)組進行匹配,由于next數(shù)組告訴了我們當出現(xiàn)mismatch時要做的事情,我們只需要當match時,兩個串往后匹配,出現(xiàn)mismatch時,模式串的指針返回到next對應(yīng)的位置即可。當模式串成功匹配到結(jié)束符時,說明找到了一個符合要求的子串。code:

    [cpp]?view plaincopy
  • char*?Search(char*?s,char*?p,const?vector<int>&?next){??
  • ????????int?pos?=?0;??
  • ????????while(*s){??
  • ????????????if(pos==-1||*s==p[pos]){//pos==-1或者匹配時,兩個指針后移??
  • ????????????????s++;??
  • ????????????????pos++;??
  • ????????????}else{????//否則模式串從pos走回到next[pos]??
  • ????????????????pos?=?next[pos];??
  • ????????????}??
  • ????????????if(-1!=pos&&!p[pos])?return?s-pos;?//匹配成功??
  • ????????}??
  • ????????return?NULL;??
  • ????}???

  • 這個就是KMP的大體框架了,網(wǎng)上也有不少的代碼,寫法不一定相同,但是思路應(yīng)該是差不多的。

    最后就是證明一下為什么這個算法復(fù)雜度是O(n+m)的了(在GetNext中我們明明有兩個循環(huán)啊~~):

    一. 首先看GetNext為什么是O(m)復(fù)雜度的:

    首先,next[i+1]<=next[i]+1。這個用反證法很容易就證明到了,如果next[i+1]>next[i]+1,那么我取一個長度為next[i+1]-1的前綴,這個前綴自然可以作為串[0,i-1]的后綴。那么,next[i]就等于next[i+1]-1,這樣就矛盾了。

    其次,對于每次while循環(huán)里面做的tmp=next[tmp],tmp的值至少減少1。這個從next本身的定義就可以推出,因為next[i]保存的是串p[0:i-1]最長的”前綴等于后綴“的長度,并且我說了,這里的前后綴不能等于串本身,所以串的前后綴的長度自然小于串本身了,所以next[tmp] < tmp。

    ? ? 最后,next[i]的值總是>=-1。這個結(jié)論比較容易得出了。

    ? ? ? ? 那么,程序的next從-1開始,每一次的for循環(huán),next最多加1,也就是說next的最大值不超過m-1。另外,如果總的運行while循環(huán)的次數(shù)不能超過m,否則,next的值將會小于-1(這里得看到,每次tmp的值都初始化為next[i])。

    所以,GetNext的while循環(huán)次數(shù)最多是m次,所以復(fù)雜度還是O(m)。


    二. Search的復(fù)雜度是O(n)的:

    ?? 首先看到,跳出循環(huán)的條件是*s等于0,那么,如果每次循環(huán)都s++的話,循環(huán)最多運行n次。這里的問題在于,當進入else的時候,并沒有執(zhí)行s++,那么,如果else運行了很多次,那么復(fù)雜度就可能不是O(n)的,我們需要證明的是,整個while循環(huán)運行過程中,else不會被進入超過n次。證明思路跟上面差不多,由于上面已經(jīng)說了pos=next[pos]使得pos至少減1,而pos不會小于-1(因為等于-1時,if的條件就成立了),然后,每次進入if時,pos只會加1,這就說明,整個循環(huán)中,進入else的次數(shù)不會多于進入if的次數(shù)+1(因為pos初始化為0)。這樣,while循環(huán)最多執(zhí)行2n次,復(fù)雜度是O(n)。


    整個算法到這里就差不多了,貼一個code,是leetcode上面的題,標準的KMP題目:


    Implement strStr()


    參考代碼:

    [cpp]?view plaincopy
  • class?Solution?{??
  • public:??
  • ????vector<int>?GetNext(char*?p){??
  • ????????vector<int>?next;??
  • ????????next.push_back(-1);??
  • ????????for(int?i=0;p[i];i++){??
  • ????????????int?tmp?=?next[i];??
  • ????????????while(tmp!=-1&&p[i]!=p[tmp])??
  • ??????????????tmp?=?next[tmp];??
  • ????????????next.push_back(tmp+1);??
  • ????????}??
  • ????????return?next;??
  • ????}??
  • ????char*?Search(char*?s,char*?p,const?vector<int>&?next){??
  • ????????int?pos?=?0;??
  • ????????while(*s){??
  • ????????????if(pos==-1||*s==p[pos]){??
  • ????????????????s++;??
  • ????????????????pos++;??
  • ????????????}else{??
  • ????????????????pos?=?next[pos];??
  • ????????????}??
  • ????????????if(-1!=pos&&!p[pos])?return?s-pos;??
  • ????????}??
  • ????????return?NULL;??
  • ????}???
  • ????char?*strStr(char?*haystack,?char?*needle)?{??
  • ????????if(!*needle)?return?haystack;??
  • ????????vector<int>?next?=?GetNext(needle);??
  • ????????return?Search(haystack,needle,next);??
  • ????}??
  • };??


  • 另外要提一下,KMP可以支持wildcard,也就是說如果母串和模式串中含有類似'.'這樣,代表可以匹配任何字符的通配符,KMP也可以處理,復(fù)雜度不會有變化。其實思路很簡單,我們在判斷字符是否相等時,用的是*s==*p,如果有通配符,我們只需要把這個等價條件改成*s==*p||*s=='.'||*p=='.'即可。為了通用一點,可以傳入一個cmp函數(shù)來處理比較邏輯,不改變代碼其他部分框架:

    [cpp]?view plaincopy
  • bool?compare(char?c1,char?c2){??
  • urn?c1=='.'||c2=='.'||c1==c2;??
  • }??
  • vector<int>?GetNext(char*?p,bool?(*cmp)(char,char)){??
  • ????vector<int>?next;??
  • ????next.push_back(-1);??
  • ????for(int?i=0;p[i];i++){??
  • ????????int?tmp?=?next[i];??
  • ????????while(tmp!=-1&&cmp(p[i],p[tmp]))??
  • ??????????tmp?=?next[tmp];??
  • ????????next.push_back(tmp+1);??
  • ????}??
  • ????return?next;??
  • }??
  • char*?Search(char*?s,char*?p,const?vector<int>&?next,bool?(*cmp)(char,char)){??
  • ????int?pos?=?0;??
  • ????while(*s){??
  • ????????if(pos==-1||cmp(*s,p[pos])){??
  • ????????????s++;??
  • ????????????pos++;??
  • ????????}else{??
  • ????????????pos?=?next[pos];??
  • ????????}??
  • ????????if(!p[pos])?return?s-pos;??
  • ????}??
  • ????return?NULL;??
  • }???
  • char?*strStr(char?*haystack,?char?*needle)?{??
  • ????if(!*needle)?return?haystack;??
  • ????vector<int>?next?=?GetNext(needle,compare);??
  • ????return?Search(haystack,needle,next,compare);??
  • } ?
  • 總結(jié)

    以上是生活随笔為你收集整理的KMP--字符串匹配的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。