从零理解的KMP算法
KMP算法的优势
KMP算法是一个效率很高的字符串匹配算法,算法大意是:给定两个字符串y,x,判断x是否在y出现过。如果暴力搜索的话复杂度为O(lenx*leny),但用KMP算法解决的话,
我们只需要一个O(lenx)的预处理,优化暴力的复杂度变成O(lenx+leny),这里lenx,leny都代表字符串的长度。
KMP算法的匹配方法
给定一个y为:ababacddsdfwwrababsababa
给定一个x为:ababa
我们先用暴力的方法跑一遍
1.
ababacddsdfwwrababsababa
ababa
↑
这里我们可以看到指针到了第一个字符时候,x和y在此指针上的字符相同;
2.
ababacddsdfwwrababsababa
ababa
↑
继续让指针向后,我们发现此时x和y的字符仍相同,那么继续
当我们跑下去会发现第一个y中含有x的字符串,继续往下跑时会发现第一个字符就不相同,那就继续往下,直到找到第一个相同的字符
3.
ababacddsdfwwrababsababa
ababa
↑
这个时候我们重复第一步,发现找到了最后一个c和b不相同,那么我们就会放弃这次查找,又从头开始匹配;
4.
ababacddsdfwwrababsababa
ababa
↑
显然这样效率太低了,那有没有办法提高效率呢???
答案当然是有的,我们可以发现在第三步的时候x的aba与y的aba是相同的,如果这一段两个字符串没法匹配,说明y中的aba一段一定不可以再匹配了,我们就可以跳过aba直接进行下一段搜索如下图
ababacddsdfwwrababsababa
ababa
↑
接下来如何判断就是KMP干的事情了
我们可以想到如果计算出来x字符串的一个转移函数next就会轻松很多。
next数组的含义就是一个字符串的其中一个字符之前的最长前缀和最长后缀相同的长度。
举个例子
abababa 最长前缀和最长后缀都是ababa
abebc 就没有最长前缀和最长后缀
qqqq 最长前缀和最长后缀就是qqq(注意不是qqqq
那么对于咱们的字符串ababa我们就可以做出处理
分别计算与a,ab,aba,abab,ababa的长度相同的最长前缀和最长后缀长度(如果下面不理解就仔细看上面的例子。
next[0]=-1,next[1]=-1,next[2]=0,next[3]=1,next[4]=2
-1表示不存在,0表示长度为1,1表示长度为2,以此类推(继续看就会知道为什么这么存
那么根据我上面说的要想把那些多余的搜索跳过就要使用这个数组,
想必大家都已经明白了
当我们搜到一个字符匹配,此时它对应的next有一个值,当他下一个字符不匹配时,可以直接移动next的值+1个长度,继续比较。
ababacddsdfwwrababsababa
ababa
↑
ababacddsdfwwrababsababa
ababa
↑
洛谷上题号P3375是这道题的模板,大家可以去练习一下
下面是我写的代码(已订正错误
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int o=0,a[10000001]; int next1[10000001],count=0; char x[10000001],y[10000001]; void cal_next(int len) { next1[0]=-1; int k=-1,q; for(q=1;q<=len-1;q++) { while(k>-1&&x[k+1]!=x[q]) { k=next1[k]; } if(x[k+1]==x[q]) { k=k+1; } next1[q]=k; } } void KmpSearch() { int i=0; int j=-1; int sLen=strlen(y); int pLen=strlen(x); while(i<sLen) { while(j>-1&&x[j+1]!=y[i]) j=next1[j]; if(x[j+1]==y[i]) { j++; } if(j==pLen-1&&y[i]==x[j]) { count++; a[o]=i-j; j=-1; i=i-pLen+2; o++; j=0; } i++; } } int main() { int n; int i; scanf("%s",y); scanf("%s",x); int lenth=strlen(x); cal_next(lenth); KmpSearch(); for(i=0;i<o;i++) { printf("%d\n",a[i]+1); } for(i=0;i<lenth;i++) { printf("%d ",next1[i]+1); } }