快速模式比對演算法(KMP)

來源:互聯網
上載者:User

    恐怕現在用過電腦的人,一定都知道大部分帶文本編輯功能的軟體都有一個快速鍵ctrl+f 吧(比如word)。這個功能主要來完成“尋找”,“替換”和“全部替換”功能的,其實這就是典型的模式比對的應用,即在文字檔中尋找串。

1.模式比對

    模式比對的模型大概是這樣的:給定兩個字串變數S和P,其中S成為目標串,其中包含n個字元,P稱為模式串,包含m個字元,其中m<=n。從S的給定位置(通常是S的第一個位置)開始搜尋模式P。如果找到,則返回模式P在目標串中的位置(即:P的第一個字元在S中的下標)。如果在目標串S中沒有找到模式串P,則返回-1.這就是模式比對的定義啦,下面來看看怎麼實現模式比對演算法吧。

2.樸素的模式比對

    樸素的模式比對演算法非常簡單,容易理解,大概思路是這樣的:從S的第一個字元S0開始,將P中的字元依次和S中字元比較,若S0=P0 && …… && Sm-1 = Pm-1,則證明匹配成功,剩下的匹配無需進行了,返回下標0。若在某一步Si != Pi 則P中剩下的字元也不用比較了,不可能匹配成功了,然後從S中第二個字元開始與P中第一個字元進行比較,同理,也是知道Sm = Pm-1或者找到某個i使得Si != S-1為止。依次類推若知道以S中第n-m個開始字元為止,還沒有匹配成功則證明S中不存模式P。(想想為什麼這裡強調是n-m)這個代碼實現應該是非常簡單的,具體開始參考strstr函數的內部實現。可以看看百度百科,給個連結http://baike.baidu.com/view/745156.htm,這裡不寫出來了,還得趕緊進入正題KMP呢。

3.快速模式比對演算法(KMP)

    樸素的模式比對效率不高的主要原因是進行了重複的字元比較。下一次比較和上一次比較沒有任何的聯絡,是樸素模式比對的缺點,其實上一次比較的比較結果是可以利用的,這就產生了快速模式比對。在樸素的模式比對中,目標串S的下標移動是一步一步的,這其實並不好,移動步數沒有必要為1。

  現在不妨假設,當前匹配情況是這樣的:S0 …… St St+1 …… St+j  與 P0 P1…… Pj ,現在正在嘗試匹配的字元是St+j+1和Pj+1,並且St+j+1 != Pj+1,言外之意就是說St St+1……St+j和P0 P1……Pj是完全符合的。那麼這個時候,S中下一次匹配開始位置應該是什麼呢??按照樸素的模式比對,下次比較應該從St+1開始,並且令St+1和P0比較,但是在快速模式比對中並不是這樣,快速模式比對選擇St+j+1和Pk+1比較,K是什麼呢?K是這樣的一個值,使得P0 P1……Pk 和 Pj-k Pj-k+1……Pj完全符合,不妨設k=next[j],因此P0 P1……Pk和St+j-k St+j-k+1 ……St+j完全符合。那麼下一次要進行匹配的兩個字元應為St+j+1和Pk+1。S和P都沒有回溯到下標0在進行比較,這就是KMP之所以快的原因啦。

    現在關鍵問題來了,這個K怎麼能得到呢?如果得到這個K值複雜度高,那這個思路就不好了,其實這個K呢,只和模式串P有關係,並且要求m個k,k = next[j],因此只要算一次儲存到next數組中就可以了,並且時間複雜度和m有關係(線性關係)。看看具體怎麼求next數組的值,即求k。

用歸納法求next[]:設next(0) = -1,若已知next(j) = k,欲求得next[j+1]。

(1)如果Pk+1 = Pj+1,顯然next[j+1] = k+1.如果Pk+1 != Pj+1,則next[j+1] < next[j],於是尋找h < k 使得P0 P1……Ph = Pj-h Pj-h+1……Pj = Pk-h Pk-h+1……Pk。也就是說h = next(k);看出來了吧,這是個迭代的過程。(也就是以前的結果對求以後的值有用)

(2)如果不存這樣的h,說明P0 P1……Pj+1中沒有前後相等的子串,因此next[j+1] =-1.

(3)如果存在這樣的h,繼續檢驗Ph和Pj是否相等。知道找到這中相等的情況,或者確定為-1求next[j+1]的過程結束。

看看實現的代碼:

View Code

int next[20] ={0};//注意返回結果是一個數組next,儲存m個k值得地方,即若next[j]=k//則str[0]str[1]…str[k] = str[j-k]str[j-k+1]…str[j]//這樣當des[t+j+1]和pat[j+1]匹配失敗時,下一個匹配位置為des[t+j+1]和next[j]+1void Next(char str[],int len){    next[0] = -1;    for(int j = 1 ; j < len ; j++)    {        int i = next[j-1];        while(str[j] != str[i+1] && i >= 0)//迭代的過程        {            i = next[i];        }        if(str[j] == str[i+1])        {            next[j] = i+1;        }        else        {            next[j] = -1;        }    }}

現在有了next數組儲存的k值,就可以實現KMP演算法了:

View Code

//des是目標串,pat是模式串,len1和len2是串的長度int kmp(char des[],int len1,char pat[],int len2){    Next(str2,len2);    int p=0,s=0;    while(p < len2  && s < len1)    {        if(pat[p] == des[s])        {            p++;s++;        }        else        {            if(p==0)             {                s++;//若第一個字元就匹配失敗,則從des的下一個字元開始            }            else            {                p = next[p-1]+1;//用失敗函數確定pat應回溯到的字元            }        }    }    if(p < len2)//整個過程匹配失敗    {        return -1;    }    return s-len2;}

時間複雜度:
  對於Next函數近似接近O(m),KMP演算法的時間複雜度為O(n),所以整個演算法的時間複雜度為O(n+m)

空間複雜度:

  多引入了O(m)的空間複雜度。

4.應用KMP的一道面試題 

  給定兩個字串是s1和s2,要判定s2是否能夠被s1做迴圈移位得到的字串包含。例如s1=AABCD,s2 =CDAA,返回true,因為s1迴圈移位可以變成CDAAB。給定s1=ACBD和s2=ACBD則返回false。

      分析:不難發現對s2移位得到的字串都將是字串s1s1的子串,如果s2可以有s1迴圈移位得到,那麼s2一定是s1s1的子串,這時KMP演算法是不是就很管用了呢。

思考:有沒有比KMP更好的思路呢??

 學習中的一點總結,歡迎拍磚哈^^

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.