转自:http://hi.baidu.com/sector/blog/item/439c14dbe69e3c3932fa1c22.html
kmp主要就是计算前缀函数e[q]=max{k,k<q且Pk < Pq},表示前缀Pq的真后缀的最长前缀的长度.
int prefixComp(char p[],int e[])
{
int m = strlen(p);
int k = 0;
e[1] = 0;
for(int i=2;i<=m;++i)
{
while(k>0&&p[(k+1)-1]!=p[i-1]) k = e[k];
if(p[(k+1)-1]==p[i-1]) ++k;
e[i] = k;
}
return m;
}
void kmp(char s[],char p[])
{
static int e[M] = {0};
int n = strlen(s);
int m = prefixComp(p,e);
int k = 0;
for(int i=1;i<=n;++i)
{
while(k>0&&p[(k+1)-1]!=s[i-1]) k = e[k];
if(p[(k+1)-1]==s[i-1]) ++k;
if( k == m )
{
printf("%d ",i-k);
k = e[k];
}
}
putchar('\n');
}
习题:试说明如何通过检查字符串PT的前缀函数e,来确定模式P在文本T中出现的位置.
解答:根据<的传递性,..< P(e[e[i]]) < P(e[i]) < Pi,即若x是P(e[e[e[..e[i]]]])的后缀,那么它是P(e[e[e[i]]])的后缀,那么也是P(e[e[i]])的后缀,..,是Pi的后缀.
int m = strlen(p);
strcat(p,s);
int e[M] = {0};
prefixComp(p,e);
int len = strlen(p);
for(int i=2*m;i<=len;++i)
{
int k = e[i];
while(k>m) k=e[k];
if( k == m ) printf("%d ",i-2*m);
}
习题:写出一个线性算法,以确定文本T是否是另一个字符串T'的循环转移.例如,arc和car是彼此的循环转移.
解答:生成文本T'TT,然后计算前缀函数,扫描文本T'TT,若e[i]>=|T'|即可.
习题:设y^i表示字符串y与其自身并置i次所得结果.例如(ab)^3=ababab.如果对某个字符串y∈S和某个r>0有x=y^r,则称字符串x∈S具有重复因子r.设w(x)表示满足x具有重复因子r的最大值r.写出一有效算法计算出w(Pi)(i=1,2,...,m),算法的输入为模式P[1,..,m],算法运行时间是多少?
解答: 先证明:Pq是基于重复因子的字符串当且仅当Pk是基于重复因子的字符串且Pk满足e[q]=max{k:Pk>Pq},q-k|k.
==>: 因为Pq是基于重复因子r的字符串,所以Pq可以表示成yyy..yy=y^r的形式,显然,Pk=y^(r-1),且q-k=r-(r-1)=1|r-1.
<==:已知Pk是基于重复因子的字符串,且Pk满足e[q]=max{k:Pk<Pq},q-k|k.因为Pq-k是Pk的前缀,Pq-k必然是Pk的因子,若Pq-k不是Pk的因子,那么存在Pq-k-k'是Pk的因子(k'<q-k),这样,Pk可以由Pq-k-k'表示成yyy..的形式,即q-k-k'|k,k'>0,这与q-k|k矛盾.因此,Pq是基于重复因子的字符串.
根据上面的命题,我们可以先计算出前缀函数e.然后对于Pi(i=1,2..,m),根据i-e[i]|e[i]来计算w.
算法如下:
int n = strlen(s);
int k = 0;
e[1] = 0;
for(int i=2;i<=n;++i)
{
while(k>0&&s[k]!=s[i-1]) k=e[k];
if(s[k]==s[i-1]) ++k;
e[i] = k;
}
for(int i=1;i<=n;++i)
{
if(i-e[i]!=0&&e[i]%(i-e[i])==0)
{
w[i] = i/(i-e[i]);
}else
{
w[i] = 1;
}
}