好佩服写kmp代码的人... 看死了终于看得有点明白了......
学kmp先看两个比较好的总结
一是大牛matrix67的 http://www.matrix67.com/blog/archives/115/
另一个是 http://www.cppblog.com/oosky/archive/2006/07/06/9486.html
这两个讲解非常好
摘一句我认为最重要的话
预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
(B数组就是模式串)额,似乎难以理解,那我就摘抄下matrix67的话吧:
假如,A="abababaababacb",B="ababacb",我们来看看KMP 是怎么工作的。我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符(j当然越大越好),现在需要检验A[i+1]和B[j+1]的关系。当A[i+1]=B[j+1]时,i和j各加一;什么时候j=m了,我们就 说B是A的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整j的位置 (减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配(从而使得i和j能继续增加)。我们看一看当 i=j=5时的情况。
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a c b
j = 1 2 3 4 56 7
此时,A[6]<>B[6]。这表明,此时j不能等于5了,我们要把j改成比它小的值j'。j'可能是多少呢?仔细想一下,我们发现,j'必须 要使得B[1..j]中的头j'个字母和末j'个字母完全相等(这样j变成了j'后才能继续保持i和j的性质)。这个j'当然要越大越好。在这里,B[1..5]="ababa",头3个字母和末3个字母都是"aba"。而当新的j为3时,A[6]恰好和B[4]相等。于是,i变成了6,而j则变成了 4:
i = 1 2 3 4 5 6 7 8 9 ……
A = a b a b a b a a b a b …
B = a b a b a cb
j = 1 2 3 4 5 67
从上面的这个例子,我们可以看到,新的j可以取多少与i无关,只与B串有关。我们完全可以预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
再后来,A[7]=B[5],i和j又各增加1。这时,又出现了A[i+1]<>B[j+1]的情况:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a b a b a cb
j = 1 2 3 4 5 67
由于P[5]=3,因此新的j=3:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a b a ba c b
j = 1 2 3 45 6 7
这时,新的j=3仍然不能满足A[i+1]=B[j+1],此时我们再次减小j值,将j再次更新为P[3]:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a ba b a c b
j = 1 23 4 5 6 7
现在,i还是7,j已经变成1了。而此时A[8]居然仍然不等于B[j+1]。这样,j必须减小到P[1],即0:
i = 1 2 3 4 56 7 8 9 ……
A = a b a b ab a a b a b …
B = a b a b a c b
j = 0 12 3 4 5 6 7
终于,A[8]=B[1],i变为8,j为1。事实上,有可能j到了0仍然不能满足A[i+1]=B[j+1](比如A[8]="d"时)。因此,准确的说法是,当j=0了时,我们增加i值但忽略j直到出现A[i]=B[1]为止。
我用的是吉大的代码(似乎写法跟主流不太一样?):
int fail[P];
int kmp(char* str, char* pat)
{
int i, j, k;
memset(fail, -1, sizeof(fail));
for(i = 1; pat[i]; ++i)
{
for(k=fail[i-1]; k>=0 && pat[i]!=pat[k+1];k=fail[k]);
if(pat[k + 1] == pat[i]) fail[i] = k + 1;
}
i = j = 0;
while( str[i] && pat[j] )
{// By Fandywang
if( pat[j] == str[i] ) ++i, ++j;
else if(j == 0)++i;//第一个字符匹配失败,从str下个字符开始
else j = fail[j-1]+1;
}
if( pat[j] )return -1;
else return i-j;
}
二是从原串中查找模式串;
poj 2406 poj 1961 都是仅仅需要理解一就行。先看poj 2406 http://poj.org/problem?id=2406
重复一下那句最重要的话:预处理出这样一个数组P[j],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[1..P[j]]=B[j-P[j]+1..j]的最大值。
如fail[i]=k;则从pat[0]到pat[i]一共i+1个字符,fail[i]=k意味着从fail[0]到fail[i-1]这i个字符0到k-1这前k个字符形成的子串与i-k到i-1这k个字符形成的子串相同,如abcdabcd,fail[5]=1,就是“abcda”,pat[0]==pat[4],所以是1,
这个例子跟poj 2406还不是很接近,那么再看一个例子:
abcabcabc 它的fail[8]=5,前5个字符为"abcab",后5个字符为“abcab”,但因为下标从0开始,并且原串就是由一个子串重复多次得到的,所以可以断言,pat[8]==pat[5],就是说前6个字符和后六个字符相同,再看看,原串长度9-6刚好等于一个周期 pe=len-fail[len-1]-1;那么周期数就是len/pe;
贴代码吧:
//141MS
#include
#include
#include
using namespace std;
#define N 1000003
int fail[N];
char str[N];
void KMP()
{
int len=strlen(str);
int i=0,k;
memset(fail,-1,sizeof(fail));
/*
k=-1;
while(i=0&&str[k+1]!=str[i];k=fail[k]);
if(str[k+1]==str[i])fail[i]=k+1;
}
int pe=len-fail[len-1]-1;
if(len%pe==0)printf("%d\n",len/pe);
else printf("1\n");
}
int main()
{
while(scanf("%s",str)!=EOF)
{
if(!strcmp(str,"."))break;
KMP();
}
return 0;
}
知道这个题再去看poj 1961 就很容易做了;
题意是,前缀如果是周期串,找出它由几个周期组成,典型的poj 2406翻版,不过是检测原串的从0到i形成的字串是不是周期串(i<=len-1)
贴代码:
#include
#include
#include
using namespace std;
#define N 1000003
int fail[N],len;
char str[N];
void kmp()
{
int i,k;
memset(fail,-1,sizeof(fail));
for(i=1;i=0&&str[k+1]!=str[i];k=fail[k]);
if(str[k+1]==str[i])fail[i]=k+1;
int t=fail[i];
if((i+1)%(i-t)==0&&(i+1)/(i-t)>1)printf("%d %d\n",i+1,(i+1)/(i-t));
}
}
int main()
{
//freopen("in.txt","r",stdin);
int ncase=1;
while(scanf("%d",&len),len)
{
printf("Test case #%d\n",ncase++);
scanf("%s",str);
kmp();
putchar('\n');
}
return 0;
}