kmp算法是有三个人联合起来缔造的一个算法,具体是谁不重要.
这种算法用于计算一个a串内b串的出现次数和位置.
这个算法的核心就是一个失配函数.
具体先看暴力算法.
暴力算法一般是这么写的.
首先维护两个指针i和j,这两个指针分别是a串搜到的位置和b串搜到的位置.
然后每次都判断一下若a[i]等于b[j],则i与j都加1.
否则就说明从这个位置无法匹配,还原.
最坏时间复杂度为O(nm).
不过一般情况下时间复杂度还是不错的.
代码如下:
inline void work(){ //这里的字符串是以a[0]和b[0]开头的,b是模板串,仅求次数sum int j=0; for (int i=0;i<n;i++){ j=0; while (a[i+j-1]==b[j]&&j<m-1) j++; if (j==m-1) sum++; } }
朴素算法很简单.
不过kmp算法更快,时间复杂度为O(n+m).
几乎是理论下界了.
我们可以这么理解kmp算法.
就像对这两个串匹配:
我们匹配先是匹配第一个位置:
在第6个位置我们会失配,这时朴素算法只是向后移一位:
依旧失配.
但kmp认为这肯定行不通,应该这样判断移几位:
判断这个位置应该移几位不应该看成是把B串向后移,而应该看成是将这个串的匹配位置放到哪里.
就是说将这个串偏移几位,主要看这个串的位置已经匹配过的有多少.
也就是说应该以哪个点作为第一个匹配的点.
就像这里应该这样偏移:
这就解释了kmp的偏移准则:重复匹配的越多越好.
kmp就认为ABABAC与ABABAB比较的过程中,最后一个字符失配了.
那么我们要移得越少越好,但是一定要是上一次失配的位置之前的字符全都匹配的情况下.
也就是说这里ABA是可以重叠的.
那么这个东西怎么算我们就需要用到字符串本身的特性了.
我们发现,我们匹配到B串是第i位的时候(字符串均以第0位开始)偏移最少,我们就得让与第i-j+1~i位相等的第0~j-1位更多.
于是我们用next[i]表示第i为的失配后最多的重叠个数.
然后我们就可设计出一个算法框架:
1.枚举一个i表示当前扫到B串的位置.
2.每一次枚举都看看B[j+1]是否等于B[i],若不是说明我们需要往前,于是j=p[j].
3.判断是否最后找到了一个可以匹配的位置,若找到了,j++.
4.p[i]=j.
很多人说这就是个自我匹配,我看挺像.
于是自我匹配部分代码如下:
inline void mateme(){ next[0]=-1; //沉痛的教训,一定要把next[0]定为-1,然后吧i从1开始枚举,不然查也查不出错来 int j=-1; for (int i=1;i<m;i++){ while ((j>-1)&&(b[j+1]!=b[i])) j=next[j]; if (b[j+1]==b[i]) j++; next[i]=j; } }
然后匹配过程就很清晰了:
inline void mate(){ int j=-1; for (int i=0;i<n;i++){ while ((j>-1)&&(a[i]!=b[j+1])) j=next[j]; if (b[j+1]==a[i]) j++; if (j==m-1) { sum++;j=next[j]; } } }
这是整个kmp算法的框架.
接下来是复杂度证明.
我们先看B匹配A的部分.
由于i肯定是枚举n次没问题.
难点在于j上.
j是不能小于0的,且让j减小的次数是不确定的.
让j增大的语句最多只能执行n次,且每次只增加1.
那么减小的语句肯定也只有最多n次执行.
所以是线性的.
同理,自我匹配部分也可以这么证明.