终于把kmp给搞完了,这两天会把所有的题以及一些重要的知识点总结一下。
这一章先讲一下kmp的部分题和重要点,kmp算法讲解可以看这几篇文章:
kmp算法主要的作用有三点:单个字符的匹配,循环节以及前后缀问题。
先抛出kmp的代码实现:
#include <iostream>
using namespace std;
const int maxn=1e6+7;
string s,p;
int nxt[maxn];
void getnext(string s){
int len=s.size();
int k=-1,j=0;
nxt[0]=-1;
while(j<len){
if(k==-1||s[j]==s[k])
nxt[++j]=++k;
else k=nxt[k];
}
}
int kmp(string s,string p){
int len1=s.size();
int len2=p.size();
getnext(p);
int i=0,j=0;
while(i<len1){
if(j==-1||s[i]==p[j])
i++,j++;
else j=nxt[j];
if(j==len2)
return i-len2+1;
}
return -1;
}
int main(int argc, char** argv) {
while(cin>>s>>p){
printf("%d\n",kmp(s,p));
}
return 0;
}
①单个字符的匹配: 这个算是kmp的模板题,不过问法通常有:求出现次数(可重叠和不可重叠),开始位置,判断是否匹配。
下面给出代码:(来自网上)
int KMP_Count(char s[], char p[]){
kmp_pre();
cnt = 0;
int i,j;
i = j = 0;
int n = strlen(s);
int m = strlen(p);
while(i < n){
while(j != -1 && s[i] != p[j]) j = nextt[j];
++i; ++j;
if(j >= m){
cnt++; //记录p在s中出现的个数
j = nextt[j]; //p在s中可以覆盖、重叠,例如s=abababa p=aba,最后cnt=3
//j = 0; //p在s中不可以覆盖、重叠,例如s=abababa p=aba,最后cnt=2
//return 1; //返回存在
//return i-m+1; //返回p首次在s中出现的下标
}
}
return cnt; //返回出现次数
//return 0; //返回不存在
// return -1; //p首次在s中出现的下标为-1,表示s中没有p
}
②循环节(next数组的妙用之一):首先要知道next数组的定义:next[i]表示s[0..i-1]的最长公共前后缀长度。
首先我们需要知道一些公式:
如果字符串循环,那么满足两个条件:i % (i-next[i]) == 0&&next[i] != 0
循环长度= i - next[i]
循环次数= i / ( i -next[i])
下面是证明——来自网上
我们先假设到达位置 i-1 的时候,字符串循环了(到i-1完毕),那么如果到第i个字符的时候,失配了,
根据next数组的求法,我们是不是得回溯?
然而回溯的话,由于字符串是循环的了(这个是假定的),
next[i] 是不是指向上一个循环节的后面一个字符呢??
是的,上一个循环节的末尾是 next[i]-1 ,然后现在循环节的末尾是 i-1 ,然么循环节的长度是多少呢?
所以,我们有 (i - 1) - ( next[i] - 1 ) = i - next[i] 就是循环节的长度(假设循环成立的条件下)
现在我们已经假设了 0————i-1 循环了,那么我们就一共有i 个字符了,
如果有 i % ( i - next[i] ) == 0,总的字符数刚好是循环节的倍数,那么说明这个循环是成立的。
注意还有一点,如果 next[i] == 0,即使符合上述等式,这也不是循环的,举个反例
0 1 2 3 4 5
a b c a b d
-1 0 0 0 1 2
下标为1,2,3的next值均为0,那么 i%(i-next【i】)=i%i==0,但是这个并不是循环。
解释完毕,然后再来看下,为什么求出来的循环节长度是最小的呢?
因为next数组失配的时候,总是回溯到最近的循环节,所以i-next【i】就是最小的循环节长度
为什么求出来的循环次数是最多的呢?
循环节长度是最小的了,那么循环次数肯定是最多的了。
③前后缀问题(next数组妙用之二):因为next表示s[0..i-1]的最长公共前后缀长度。我们可以通过一个while循环,不断迭代,直到nextt[i]为0,可以求出字符串所有的前后缀相等的长度。
例如,ababcababababcabab,最长的相等长度为nextt[18]=9(ababcabab),然后nextt[9]=4(abab),nextt[4]=2(ab), nextt[2]=0。
注意,括号的字符串既可以理解为前缀,也可以理解为原字符串的前后缀的组合(由于之前前后缀的匹配带来的关系)。