1. 程式人生 > >KMP演算法總結及相關例題

KMP演算法總結及相關例題

KMP演算法總結

相關介紹KMP演算法的文章很多,在這裡並不累述,主要寫一下其中的要點

KMP演算法主要是兩個步驟

 1. getnext : 生成next表(時間複雜度O(lenP))

      next[i] 表示下標範圍 0~i 的P(pattern)字串的最長公共字首字尾的長度。

 2.kmp:  字串匹配(時間複雜度O(lenT))

      在T(target)串中匹配P(pattern)串

這兩個部分程式碼完全相似。

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

int main()
{
    //getnext:生成next表(字首字尾的最大overlap長度)
    int next[999];
    char T[]="aabbbcbabbbaabbaaa",P[]="abba";
    int lenT=strlen(T),lenP=strlen(P);
    next[0]=0;
    next[-1]=0;
    for(int i=1;i<lenP;i++)
    {
        int j=next[i-1];
        while(P[j]!=P[i] && j!=0) j=next[j-1];
        next[i]=(P[j]==P[i])? j+1 : 0;
    }

    //kmp:在T(target)串中匹配P(pattern)串
    int j=0;
    for(int i=0;i<lenT;i++)
    {
        while(P[j]!=T[i] && j!=0) j=next[j-1];
        if(P[j]==T[i]) j++;
        if(j==lenP) printf("%d\n",i-lenP+1);
    }
    return 0;
}

生成next表是用自己遞推自己的方式實現的,大致思想就是每次能不能接到目標位置後面,若能則結果是這個位置匹配數量+1;否則結果就是0個公共字首字尾;

字串匹配大致思想是當每次失配時,就沿著next表找到新的匹配點,這樣避免了無用的比較。

注意:

1、迴圈節

當前綴字尾串有重疊時,錯開的那一部分的長度如果能整除字串的長度,那麼錯開的這一部分便是字串的一個迴圈節。

2、匹配次數

每次匹配過後還要繼續匹配,注意可能會有匹配串overlap,所以更新的匹配長度為next[j-1](詳見下面最後一個例題)

KMP演算法例題

POJ 2752 公共字首-字尾串(用kmp的next表)

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

char s[400005];
int next[400005];
int len;

void getnext()
{
    next[-1]=0;next[0]=0;
    for(int i=1;i<len;i++)
    {
        int j=next[i-1];
        while(s[j]!=s[i] && j>0) j=next[j-1];
        next[i] = (s[j]==s[i])? j+1:0;
    }
}

void print(int t)
{
    if(next[t]>0)
    {
        print(next[t]-1);
        printf("%d ",next[t]);
    }
}

int main()
{
    while(scanf("%s",s)>0)
    {
        len=strlen(s);
        getnext();
        print(len-1);
        printf("%d\n",len);
    }
    return 0;
}

LA3026迴圈節(用kmp的next表)

注意怎麼判斷迴圈節!

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

char s[1000005];
int next[1000005];

void getnext(int len)
{
    //生成next表(字首字尾的最大overlap長度)
    next[0]=0;
    next[-1]=0;
    for(int i=1;i<len;i++)
    {
        int j=next[i-1];
        while(s[j]!=s[i] && j!=0) j=next[j-1];
        next[i]=(s[j]==s[i])? j+1 : 0;
    }
}

int main()
{
    int n,t=1;
    scanf("%d",&n);
    while(n!=0)
    {
        printf("Test case #%d\n",t++);
        scanf("%s",s);
        int len=strlen(s);
        getnext(len);
        for(int i=2;i<=len;i++)
            if(next[i-1]>0 && (i%(i-next[i-1]))==0) printf("%d %d\n",i,i/(i-next[i-1])); //判斷迴圈節
printf("\n"); scanf("%d",&n); } return 0; }
POJ 2406(用kmp的next表)
#include<stdio.h>
#include<string.h>

char s[1000005];
int next[1000005];

void getnext()
{
    //生成next表(字首字尾的最大overlap長度)
    int len=strlen(s);
    next[0]=0;
    next[-1]=0;
    for(int i=1;i<len;i++)
    {
        int j=next[i-1];
        while(s[j]!=s[i] && j!=0) j=next[j-1];
        next[i]=(s[j]==s[i])? j+1 : 0;
    }
}

int main()
{
    scanf("%s",s);
    while(s[0]!='.')
    {
        int ans=1;
        int len=strlen(s);
        getnext();
        if((len%(len-next[len-1]))==0) ans=len/(len-next[len-1]);
        printf("%d\n",ans);
        scanf("%s",s);
    }
    return 0;
}

POJ 3461 字串匹配次數(kmp)

每次匹配過後還要繼續匹配,注意可能會有匹配串overlap,所以更新的匹配長度為next[j-1]

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

char P[10005],T[1000005];
int next[10005];
int sum;

void kmp()
{
    //生成next表(字首字尾的最大overlap長度)
    int lenT=strlen(T),lenP=strlen(P);
    next[0]=0;
    next[-1]=0;
    for(int i=1;i<lenP;i++)
    {
        int j=next[i-1];
        while(P[j]!=P[i] && j!=0) j=next[j-1];
        next[i]=(P[j]==P[i])? j+1 : 0;
    }

    //在T(target)串中匹配P(pattern)串
    int j=0;
    for(int i=0;i<lenT;i++)
    {
        while(P[j]!=T[i] && j!=0) j=next[j-1];
        if(P[j]==T[i]) j++;
        if(j==lenP)
        {
            sum++;
            j=next[j-1];  //字串匹配計數時要這樣修改!代表我下一次匹配之前已經匹配了next[j-1]位了
        }
    }
}

int main()
{
    int n;
    scanf("%d",&n);
    for(;n>0;n--)
    {
        sum=0;
        scanf("%s",P);
        scanf("%s",T);
        kmp();
        printf("%d\n",sum);
    }
    return 0;
}