KMP演算法總結及相關例題
阿新 • • 發佈:2019-02-11
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;
}