1. 程式人生 > >KMP演算法C程式碼實現

KMP演算法C程式碼實現

一、初識KMP

    理解KMP演算法需要關注2個問題:(請注意:字串下標從0開始。)

    當i指標與j指標失配時:

    1、當母串和模式串不匹配時,i指標為什麼不需要回溯?

    2、當母串和模式串不匹配時,i指標不回溯,那麼j指標應該移動到哪?

    通過解釋第1個問題,我來引出第2個問題。(其實第1個問題,你不明白,不影響你研究第2個問題,你也可以理解了第2個問題後來分析第1個問題。)

    對於母串S和模式串T,如果母串的指標i處與模式串的j處失配,假設母串存在回溯i到back(i) (匹配前的i < back(i) < i),有與模式串的從0開始的模式串有一段一樣長的“段落”,這個母串的段落只有是S[back(i)] 至S[i-1]才有意義,如果從S[back(i)] 至S[i-1]的過程中就已經不匹配了,那回溯至這個back(i)就更沒有必要了。 即S[back(i) ...i-1]=T[0...x] ,x = i-1-back(i)。又因為前面失配時存在同樣長度的段落,即S[back[i]...i-1] = T[ j -1-x...  j-1],所以T[0...x] = T[j-1-x ...j-1]。

   這其實就轉化成了i不變,j指標移動到x+1(後面討論用next[j] 表示 x +1 )的問題,即問題2。

   你可以用back(i) = i - 1 - x = i - next[j]來驗證我的分析,注意我的字串的下標是從0開始的。

    我們來討論問題2:


    當 i與j 不匹配的時候,圖1所示,因為有k < j 並且 Si-j... Si-1  = T0...Tj-1 ,所以Si-k...Si-1  = Tj-k...Tj-1。

    由於S的i不回溯,圖2所示,且需要與T的k指標比較,則Si-k...Si-1 = T0...Tk-1。所以T0...Tk-1 = Tj-k...Tj-1。

    令next[j] = k 表示當模式串的j與主串相應字元i不匹配時,在模式中需重新與主串中該字元i比較的字元的位置。則next[j]表示為:


    那我們如何求取next[j]呢? 用遞推的方式,即通過已知的next[0] = -1, 來推出後面如next[1]、next[2]、next[3]... 的值。最關鍵的是找出next[j + 1] 與next[j]的關係。

    所以我們來研究next[j  + 1] 與 next[j]的關係。(當然我們的前提是next[j] = k,不要忘記哦)

    因為 next[j] = k,這意味著T0...Tk-1= Tj-k ...Tj-1 (0 < k < j,並且k是滿足這個等式的max(k)。)

    那此時next[j + 1] 等於多少呢?

    分兩種情況:

    1、如 Tk = Tj,則T0...Tk-1Tk = Tj-k...Tj-1Tj 。這表示next[j + 1] = k + 1,又由於k = next[j] ,所以next[j + 1] = next[j] + 1 ,故根據next[j]可求得next[j+1]。

    2、較為難理解的是Tk ≠ Tj 。此時顯然T0...Tk-1Tk   Tj-k...Tj-1Tj 。如果我們把###Tj-k...Tj-1Tj### 表示母串,以T0...Tk-1Tk###表示模式串。顯然當Tj與Tk失配的時候,Tj應該與模式串的k'=next[k] 來匹配。此時應該有T0...Tk'-1 = Tk-k'...Tk-1=Tj-k'...Tj-1。(1 < k' < k < j)。 注意,此時我們需要關注Tk‘與Tj的關係,關注Tk'與Tk的關係對於解決next[j+1]沒有用處!

    此時也應該分兩種情況,即

    1)、如果Tk‘= Tj ,所以T0...Tk'-1Tk' = Tj-k'...Tj-1Tj 。所以next[j + 1] = k' + 1,由於k' = next[k],所以next[j + 1] = next[k] + 1,故根據next[k]可求得next[j+1]。

    2)、如果Tk' ≠ Tj,我們就要尋找更小的k''來匹配Tj。這當然也是有兩種情況,即Tk'' = Tj 或Tk"≠ Tj,對於前者,next[j + 1] = k" + 1 = next[k'] + 1,尋找結束;對於後者,繼續尋找更小的k'''、k'''',當然尋找不能無限制下去,結束的條件就是next[0] = -1 或你提前找到了你的k ?(如果你找到了你的k?,顯然next[j + 1] = k?+ 1,尋找結束)。

   現在來分析說明get_next函式(見程式碼實現部分)的正確性。

    我們需要證明在next[j]==k的基礎上得到next[j+1]=(k/next[k]/.../-1之一) + 1 ,考察字串abaabcac,下標從0開始,規定next[0]=-1

    當j=0, k=-1,滿足next[j]=k,因為k=-1,我們可以得到next[j+1]=k+1, 即next[1]=0;

    當j=1, k=0, 滿足next[j]=k,因為T[j] T[k],但next[k]=-1,我們可以得到next[j+1]=next[k]+1, 即next[2]=0;

    當j=2, k=0, 滿足next[j]=k,因為T[j]=T[k], 我們可以得到next[j+1]=k+1, 即next[3]=1;

    當j=3, k=1, 滿足next[j]=k,因為T[j]T[k],但T[j]=T[next[k]], 我們可以得到next[j+1] = next[k]+1, 即next[2]=1;

    由前面next[j] = k ,我們根據get_next函式可以證明一般性結論next[j+1] = (k/next[k]/.../-1之一) + 1。

二 、程式碼實現

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

int next[32] = {-999};

/* 返回模式串T在母串S中第pos個字元的位置 */
/* 除錯小技巧 print x = value 或 set var x = value 可以改變gdb執行時變數的值 */
int index_BM(char *S, char *T, int pos)
{
    int i;
    int j;

    i = pos;
    j = 0; 

    while ( (i < strlen(S)) && (j < strlen(T)) )
    {
        if (S[i] == T[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i - j + 1; 
            j = 0;
        }
    }

    /* 注意strlen(T)意味著j的取值範圍為0 ~ (strlen(T) - 1) */
    if (strlen(T) == j)
    {
        return i - strlen(T);
    }
    else
    {
        return -1;
    }
}

void get_next(char *T, int *next)
{
    int k = -1;
    int j = 0;

    next[j] = k;

    while (j < strlen(T))
    {
        if ( (k == -1) || (T[j] == T[k]) ) //注意等號是==,而不是=
        {
            ++k; // 注意是先加後使用
            ++j;
            next[j] = k;
        }
        else
        {
            k = next[k]; 
        }
    }
}

int index_KMP(char *S, char *T, int pos)
{
    int i;
    int j;

    i = pos;
    j = 0; 

    while ( (i < strlen(S)) && (j < strlen(T)) )
    {
        /* j = -1 表示next[0], 說明失配處在模式串T的第0個字元。所以這裡特殊處理,然後令i+1和j+1。*/
        if ( (j == -1)  || S[i] == T[j])
        {
            i++;
            j++;
        }
        else
        {
            j = next[j];
        }
    }

    if (strlen(T) == j)
    {
        return i - strlen(T);
    }
    else
    {
        return -1;
    }
}

void print_next(int next[], int n)
{
   int i;

   for (i = 0; i < n; i++) 
   {
       printf("next[%d] = %d\n", i, next[i]);
   }
}

int main(void)
{
    char *s = "ababcabcacbab";
    char *t = "abcac";
    int pos = 0;
    int index;

    printf("================ BM ==============\n");
    index = index_BM(s, t, pos);
    printf("index = %d\n", index);

    printf("================ KMP ==============\n");
    get_next(t, next);
    print_next(next, strlen(t));

    index = index_KMP(s, t, pos);
    printf("index = %d\n", index);
}