我觉得应该有很多人在学kmp的时候和我一样,找了很多的博客教程看了很久都没看懂,反而越看越乱。。。
kmp是什么
kmp是一种基础的字符串匹配算法,简单来说就是一种飞快的匹配,比如你想找b字符串在a字符串中是不是出现了,你就不需要两重循环枚举a的字符和枚举b的字符。
就比如原串aabacabaa,模式串abaa
- 假如现在匹配到这里,前面三个aba都是匹配,第四个不匹配,按照暴力的匹配方法,应该是模式串回到1,然后原串从下一个字符开始
aabacabaa
abaa - 也就是
aabacabaa
abaa - 但如果是kmp的匹配方式,就应该是
aabacabaa
abaa - 理由就是模式串
abaa的第四个字符nex[4]等于1,代表前三个字符里,最大的相同前缀后缀长度是1,也就是aba里a和a是相同的,一个前缀一个后缀,所以,我们刚才是匹配到第四个字符才不匹配,那说明前三个是匹配的,也就是刚才说的那个前缀和后缀都是匹配过的,那我们是不是就可以直接认为模式串前缀那部分是匹配过的,直接移到后缀那里(重叠),直接从后缀的下一个开始匹配即可。 - 打字出来可能不太好理解,但画个图可能更好理解
- 如果不用kmp的话,失配后应该是模式串后移一位,而这里kmp是后移了两位(这就是为什么飞快),为什么能后移两位呢,因为我们已经能确定原串第3个的b和模式串第1个的a不相等,没必要比一次,为什么能确定不相等呢,因为nex数组,如果b和a相等,那这里的nex数组就对应不是1了,而是2了,总之记住nex[i]表示前i个字符的最长前缀后缀相等的长度
nex数组
前面说了,nex数组是一个很有用的东西,nex[i]定义的就是前i个字符的最大相同前缀后缀的长度
怎么求这个nex数组呢
先给个我觉得比较短的模版
void getNext(string s){
int n=s.size();
int i=0;
int j=-1;
nex[0]=-1;
while(i<n){
if(j==-1 || s[i]==s[j]){
nex[++i]=++j;
}else{
j=nex[j];
}
}
}
注意nex数组求的是模式串
使用递推来求解
比如abaccaba这个我们现在求最后一个字符的nex,也就是已知nex[n-1]=2(ab),可以很直观的看出,如果前缀和后缀这个ab后面的字符相等的话,那nex[n]就可以变成j+1=3
如果不相等,那j就回到nex[j]
kmp的用法
- 直接匹配子串是否出现/出现次数
- 循环节n-nex[n] 循环节出现次数n/(n-nex[n])
- 和前缀有关的一些dp