數據結構--KMP算法總結


數據結構—KMP

    KMP算法用於解決兩個字符串匹配的問題,但更多的時候用到的是next數組的含義,用到next數組的時候,大多是題目跟前后綴有關的 。

首先介紹KMP算法:(假定next數組已經學會,后邊next數組會在介紹)

上圖T為主鏈,P為模板鏈,要求P在T中是否出現,出現就返回位置。

朴素算法會順序遍歷,比較第一次的時候p[0]處失配,然后向后移動繼續匹配。數據量大的時候這么做肯定是不可行的。所以這里就會有KMP算法!在一次失配之后,KMP算法認為這里已經失配了,就不能在比較一遍了,而是將字符串P向前移動(已匹配長度-最大公共長度)位,接着繼續比較下一個位置。這里已匹配長度好理解,但是最大公共長度是什么吶?這里就出現了next數組,next數組:next[i]表示的是P[0-i]最大公共前后綴公共長度。這里肯定又有人要問了,next數組這么奇葩的定義,為什么就能算出來字符串需要向后平移幾位才不會重復比較吶?

上圖中紅星標記為例,此時在p[4]處失配,已匹配長度為4,而next[3]=2(也就是babaa中前后綴最大公共長度為0),這時候向后平移已匹配長度-最大公共長度=2位,P[0]到達原來的P[2]的位置,如果只平移一位,P[0]到達p[1]的位置這個位置沒有匹配這次操作就是無用功所以浪費掉了時間。已知前綴后綴中的最大公共長度,下次位移的時候直接把前綴位移到后綴上面直接產生匹配,這樣直接從后綴的后一位開始比較就可以了。這樣將一下無意義的位移過濾掉剩去了不少的時間。

下面講解next數組通過語言進行實現:

void makeNext(const char P[],int next[])
{
    int q,k;
    int m=strlen(P);
    next[0]=0;
    for (q=1,k=0;q<m;++q)
    {
        while(k>0&&P[q]!=P[k])
            k = next[k-1];
        /*
        這里的while循環很不好理解!
        就是用一個循環來求出前后綴最大公共長度;
        首先比較P[q]和P[K]是否相等如果相等的話說明已經K的數值就是已匹配到的長的;
        如果不相等的話,那么next[k-1]與P[q]的長度,為什么吶?因為當前長度不合適
        了,不能增長模板鏈,就縮小看看next[k-1]
        的長度能夠不能和P[q]匹配,這么一直遞歸下去直到找到
        */
        if(P[q]==P[k])//如果當前位置也能匹配上,那么長度可以+1
        {
            k++;
        }
        next[q]=k;
    }
}

 

上面KMP算法的理論部分已經講解完了,下面解釋語言實現:

int kmp(const char T[],const char P[],int next[])
{
    int n,m;
    int i,q;
    n = strlen(T);
    m = strlen(P);
    makeNext(P,next);
    for (i=0,q=0;i<n;++i)
    {
        while(q>0&&P[q]!= T[i])
            q = next[q-1];
        /*
        這里的循環就是位移之后P的前幾個字符能個T模板匹配
        */
        if(P[q]==T[i])
        {
            q++;
        }
        if(q==m)//如果能匹配的長度剛好是T的長度那么就是找到了一個能匹配成功的位置
        {
            printf("Pattern occurs with shift:%d\n",(i-m+1));
        }
    }
}
 

另外KMP算法還可以進一步的優化:

/*************************KMP模板****************************/
int next[101];//優化后的失配指針,記住這里next要比P多一位,因為P到m-1即可,但是next還要計算出m的失配指針
int next2[101];//next2用來保存KM指針,是為優化next的失配指針,next保存的是優化之后的失配指針
char T[1000];//待匹配串
char P[100];//模板串
void makeNext(char *P, int *next)
{
    int m = strlen(P);
    next[0]=next[1]=0;
    next2[0]=next2[1]=0;
    for(int i=1;i<m;i++)
    {
        int j = next2[i];
        //這里直接找出當前位置上一步的next,和上一步不斷保存K值是一個道理
        while(j && P[i]!=P[j]) 
            j = next2[j];
        next2[i+1]=next[i+1]=(P[i]==P[j])?j+1:0;
 
        //既然i+1的失配位置指向j+1,但是P[i+1]和P[j+1]的內容是相同的
        //所以就算指針從i+1跳到j+1去,還是不能匹配,所以next[i+1]直接=next[j+1]
        if(next[i+1]==j+1 && P[i+1]==P[j+1]) //這一步就是進行優化,如果下一個位置還能和當前位置匹配那么直接更新next數組的值
            next[i+1]=next[j+1];
    }
}
void kmp(char *T, char *P, int *next) //找到所有匹配點
{
    int n = strlen(T);
    int m = strlen(P);
    int j = 0;
    for(int i = 0; i < n; i++)
    {
        while(j && T[i] != P[j]) j = next[j];//向前移動了多少
        inext(T[i] == P[j]) j++;
        inext(j == m) printnext("%d\n", i - m + 1);
    }
}
/*************************KMP模板****************************/

 

擴展KMP算法

這里稍稍的提一點,時間倉促,我也還沒有徹底的理解……嘖嘖嘖

理論部分如果我講的不好別噴,求T與S[i,n-1]的最長公共前綴extend[i],要求出所有extend[i](0<=i<n)。下面從模板中講解:

const int maxn=100010;   //字符串長度最大值
int next[maxn],ex[maxn]; //ex數組即為extend數組
/*
extend數組,extend[i]表示T與S[i,n-1]的最長公共前綴,要求出所有extend[i](0<=i<n)。
*/
 
/*
設輔助數組next[i]表示T[i,m-1]和T的最長公共前綴長度
*/
 
//預處理計算next數組
void GETNEXT(char *str)
{
    int i=0,j,po,len=strlen(str);
    next[0]=len;//初始化next[0]
    /*
    0到n-1組成的字符串和str的最長公共前綴長度當然是len了
    */
    while(str[i]==str[i+1]&&i+1<len)//計算next[1],也就是第一位的時候能匹配多少
    i++;
    next[1]=i;

    po=1;//初始化po的位置
    for(i=2;i<len;i++)
    {
        if(next[i-po]+i<next[po]+po)//第一種情況,可以直接得到next[i]的值
        /*
        如果不如之前計算過的最長的長就直接賦值為最長的那個
        */
        next[i]=next[i-po];
        else//第二種情況,要繼續匹配才能得到next[i]的值
        /*
        比最長的還短,那么后面的就不是到了,所以要繼續匹配
        */
        {
            j=next[po]+po-i;
            if(j<0)j=0;//如果i>po+next[po],則要從頭開始匹配
            while(i+j<len&&str[j]==str[j+i])//計算next[i]
            j++;
            next[i]=j;
            po=i;//更新po的位置
        }
    }
}
//計算extend數組
void EXKMP(char *s1,char *s2)
{
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);
    GETNEXT(s2);//計算子串的next數組
    while(s1[i]==s2[i]&&i<l2&&i<len)//計算ex[0]
    i++;
    ex[0]=i;
    po=0;//初始化po的位置
    for(i=1;i<len;i++)
    {
        if(next[i-po]+i<ex[po]+po)//第一種情況,直接可以得到ex[i]的值
        ex[i]=next[i-po];
        else//第二種情況,要繼續匹配才能得到ex[i]的值
        {
            j=ex[po]+po-i;
            if(j<0)j=0;//如果i>ex[po]+po則要從頭開始匹配
            while(i+j<len&&j<l2&&s1[j+i]==s2[j])//計算ex[i]
            j++;
            ex[i]=j;
            po=i;//更新po的位置
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM