KMP用于线性时间内处理字符串匹配问题.
KMP算法的代码实现不难,难点在于对失配指针,也就是next数组的理解.
关于这个理解,各种神犇的博客都有很全面地分析,我就不再过多地赘述了,只结合代码简单分析一下.
首先,KMP算法就两个部分,而且还长得特别地像.
第一个部分是预处理,也就是模式串B(用来匹配的串)的"自我匹配"过程,这个过程是用来求next数组的.
第二个部分是B与主串A(等待匹配的串)的匹配过程.
我们用i,j指针表示(A[i-j+1...i])和(B[1...j])完全相等.i是主串A的指针,它在不断地变化,随着i的增加,模式串B的指针j也应相应地变化,才能始终保证上述"(A[i-j+1...i])和(B[1...j])完全相等".
那么指针j应该如何"相应地变化"呢?就引入了next数组.
(next[j])记录的是当模式串B匹配到下标j位置时,不能再与主串A匹配下去(即当(A[i+1]!=B[j+1])时),指针j能够向前跳到的(下标)最大位置.这个位置(假设下标是k),应当满足的条件是(B[1...k]=B[j-k+1...j]),简单来说就是既然我们不能保证B串的前j个都能匹配了,那么退而求其次,我可以保证B串的前k个(k<j)仍然匹配(如果这也是奢望,那就一直退而求其次,直到不能再退,即k=0)
int j=0;
nxt[1]=0;
//初始化:匹配到第一个位置不能匹配了,那就只能到0了
for(int i=1;i<n;i++){
while(j>0&&s2[i+1]!=s2[j+1])j=nxt[j];
//不能继续匹配且j还没减到0,考虑退一步
if(s2[i+1]==s2[j+1])j++;
//能匹配,j的值+1
nxt[i+1]=j;
//记录第i+1个位置的失配指针位置
}
第二个部分就是由B串自己跟自己匹配变为B串与A串匹配,所以过程是极其相似的.
int j=0;
for(int i=0;i<m;i++){
while(j>0&&s1[i+1]!=s2[j+1])j=nxt[j];
if(s1[i+1]==s2[j+1])j++;
if(j==n){
printf("%d
",i+1-n+1);
//模式串(子串)串首在主串(母串)中的位置
ans++;
//模式串(子串)在主串(母串)中的出现的次数
j=nxt[j];
//这里是为了让程序继续运行下去
//因为有可能后面还有合法的匹配.
}
}
KMP模板(用上面这两段就能应付了)
[BOI2009]Radio Transmission 无线传输
题意:给你一个字符串,它是由某个字符串不断自我连接形成的.但是这个字符串是不确定的,现在只想知道它的最短长度是多少?
样例输入:
8
cabcabca
样例输出:
3
样例解释:abc,cab,bca都符合题意.
本题灵活运用到了next数组的性质:(next[j])能够始终保证(B[1...j]=B[i-j+1...i]),本题的实质就是求字符串的循环节,所以j与(next[j])的距离就是本题的答案.(前提是(next[j])不为零),所以输出(n-next[n])最可靠.
[POI2006] OKR-Periods of Words
本题难点在于读懂题意,题目意思就是给定一个字符串,对于该字符串的每一个前缀x,求该前缀x的 最长的前缀y,且满足x又是yy(相当于两个y合并)的前缀.答案就是所有的这些最长的且符合要求的前缀的长度之和.
根据next数组的性质,我们只要让失配指针一直失配下去,直到不能失配为止(即到达边界下标0处)
long long ans=0;//十年oi一场空...
for(int i=1;i<=k;i++){
if(nxt[nxt[i]])
nxt[i]=nxt[nxt[i]];
}
for(int i=1;i<=k;i++)
if(nxt[i])ans+=i-nxt[i];
printf("%lld
",ans);
小结一下,KMP算法的代码不难实现,但重点是能够理解next数组,失配指针的含义,从而能够得到很多性质.而大多数有关KMP算法的题目就是利用这些性质来巧妙解题的,代码量都不会很大.