練習:KMP(字符串模式匹配問題)

KMP算法是模式串匹配算法中最爲著名的一個,其他的還有BM、Horspool、Sunday等。

這篇文章,對各種算法有比較全面的介紹。但是,其中代碼尚存在問題,不能照搬,重在理解各種算法思想。

KMP算法應用最多(至少在ACM競賽中出現頻率很高),因此需要深入理解,熟練掌握。其O(n+m)的時間複雜度,也是非常出色的。

在我看過的幾篇材料中,這篇文章,對KMP算法的介紹比較透徹。可惜的是,有些地方略顯冗長,細節和例子講得過細了,結果是無法很快掌握KMP算法的精髓。

掌握KMP算法的精髓,抓住這幾點:

1. 理解“移動”和“跳轉”的區別

    “移動”只不過是人們考慮問題的角度,事實上,編程時是藉助“跳轉”實現的,即只改變串的索引。這就是理解和編程時的思維差別,不要總在腦子裏打轉轉。

2. KMP與暴力匹配只有一步之遙

    KMP的思想非常簡單:在暴力匹配失敗時,採取不同的“跳轉”方式,使得模式串儘可能向後“移動”。暴力匹配中,一旦失敗,模式串相對於原串總是一個字符一個字符地向後“移動”,再從模式串的頭部開始匹配;而KMP中,模式串可以一次向後“移動”好幾步,並且不一定重新從模式串的頭部開始匹配,減少了不必要的匹配次數。

3. KMP跳轉的依據:next數組

    Next數組可以理解爲模式串的屬性,與原串無關。Next[i]的值,表示模式串中索引爲i的位置一旦匹配失敗,則模式串的索引立即由i變爲next[i],而原串的索引不變,並將原串當前索引的字符與模式串索引爲next[i]的字符繼續匹配下去。

4. Next數組計算的依據:已匹配子串的對稱性

    KMP要解決的問題,就是暴力匹配常常“功虧一簣”。暴力匹配往往已經匹配成功很長的一段,卻在最後幾個字符上出了問題。KMP要利用已經匹配成功的子串,假如該子串有相同的前綴和後綴,那麼就將模式串向後“移動”,使得前綴部分完全覆蓋原來的後綴部分,模式串接下來的匹配只需要從覆蓋部分的下一個字符開始(即前綴的下一個字符開始)。如果模式串的索引是從0開始編號的,那麼可以發現next[i]的值恰好是前綴長度。

5. Next數組的實現

    理解了上面的部分,可以說KMP的思想已經吃透了。剩下的最大障礙,就是編程實現計算next數組,顯然,如果對於每一個i,都列出之前子串的所有前綴、後綴,然後找到最長公共前綴後綴,最後將next[i]的值賦爲該長度,這不是一個現實的辦法。事實上,我們可以通過0,1,2……i-1中已經計算出的next值,得出next[i]值。這一點,在很多文章或者書籍中已經介紹得很詳細了,這裏不再贅述。值得注意的是,next[i]和i兩個索引指向的模式串字符不應相等,否則即使跳轉了,接下來比較的還是兩個一模一樣的字符,肯定重蹈覆轍。這一點便是優化KMP算法的出發點,同樣在很多文章和書籍中有所提及,不再贅述。


下面附上我寫的經過測試的KMP代碼,供大家參考。

#include <stdio.h>
#include <string.h>

void getNext(char t[], int next[]) //建立next數組
{
    int i=0, j=-1, tLen=strlen(t);
    next[i]=j; //next[0]=-1

    while (i<tLen)
        if (j==-1 || t[i]==t[j])
        {
            i++;j++;
            if (t[i]!=t[j]) next[i]=j; else next[i]=next[j]; //KMP算法的優化
        }
        else j=next[j];
}

int kmpMain(char s[], char t[], int next[], int pos) //從原串s中索引爲pos處開始匹配模式串t
{
    int i=pos, j=0, sLen=strlen(s), tLen=strlen(t);

    while (i<sLen && j<tLen)
        if (j==-1 || s[i]==t[j]){i++;j++;}
        else j=next[j];

    if (j==tLen) return i-j; //返回匹配成功開始的地方
    else return -1; //未找到任何模式串
}

int main()
{
    char s[1000],t[1000];
    int next[1000];
    scanf("%s",s);
    scanf("%s",t);

    getNext(t,next);
    printf("%d",kmpMain(s,t,next,0));

    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章