KMP算法是一种模式匹配算法,用于字符串寻找子串,在BF算法(暴力)中由于在匹配中有多次匹配是多余的情况,针对这种情况做的优化改进。
首先我们需要了解几个基本的概念:字符串的前缀,后缀和部分匹配值
1、前缀是指除最后一个字符以外,字符串所有的头部子串
2、后缀是指除第一个字符以外,字符串所有的尾部子串
3、部分匹配值则为字符串最长相等前后缀的长度。
以‘ababa’为例:
'a'前后缀都为空集,最长相等前后缀的长度为0;
'ab'前缀为{a},后缀为{b},最长相等前后缀的长度为0;
'aba'前缀为{a,ab},后缀为{ba,a},最长相等前后缀的长度为1;
'abab'前缀为{a,ab,aba},后缀为{bab,ab,b},最长相等前后缀的长度为2;
'ababa'前缀为{a,ab,aba,abab},后缀为{baba,aba,ba,a},最长相等前后缀的长度为3;
所以'ababa'这个字符串的部分匹配值为00123,写为PM表的形式:
编号 | 1 | 2 | 3 | 4 | 5 |
S | a | b | a | b | a |
匹配值 | 0 | 0 | 1 | 2 | 3 |
用PM表来做匹配过程:
第一次匹配:
在j=5失配,i不动,把j回退到2位,此时j=3,从图像上来看就相当于整个子串后移了两位,实际上移动的只有j;
第二次匹配:
由于不同的还是i=5的部分,i不动,j后移2位变为1,开始第三次匹配:
第四次匹配:
结束。
由于PM表在使用的时候并不方便,因此对于上述的PM表,把匹配值那一行全部右移一位,空缺补-1,再全体+1,就得到了next数组(有的题目里不+1):
编号 | 1 | 2 | 3 | 4 | 5 |
S | a | b | a | b | a |
next | 0 | 1 | 1 | 2 | 3 |
此时,next[j]的含义是:在子串的第j个字符与主串发生失配时,j则跳到子串next[j]位置,重写与主串当前位置进行比较。
在做题的过程中一般直接求next数组,next[1]=0:
在这里解释next[]数组里关于0,1,以及其他数字的具体含义:
0代表初始化,也可以理解为将主串和模式串的第1个字符前面的空位置对齐,即模式串右移一位
1代表j将回跳到模式串开头,意味着此时子串主串匹配率最低,右移位数最大
其他数字自然是代表不用从头开始匹配,可以从next[j]位置开始匹配拉
以上过程是简单手算的过程,代码实现的话难度有点大,而且,代码求next数组的过程和手算并不一样,要用到递归的思想,我也没有理解得很好QAQ,所以参考了王道的代码和解释:
void getNext(String T,int next[]){
int i=1,j=0;
next[1]=0;
while(i<T.length){
if(j==0||T.data[i]==T.data[j]){
++i;++j;
next[i]=j;//若T[i]==T[j],则next[j+1]=next[j]+1
}
else j=next[j];//否则令j=next[j],循环继续
}
}
KMP算法的优化:(比如像'aaaab'匹配'aaabaaaaab'的情况还可以进一步优化)
打字太多了懒得搞了,直接上个公式做题把Q_Q
next数组更名为nextval数组,分两种情况:
j=1时,nextval[j]=0;
j>1时:
若T[ j ]!=T[ next[j] ] nextval[ j ]=next[ j ]
若T[ j ]==T[ next[j] ] nextval[ j ]=nextval[ next[j] ]
总结:KMP算法复杂度是O(m+n),虽然暴力匹配是O(mn),但在一般情况下近似为O(M+N),而且KMP算法仅在主串和子串有很多部分匹配下才现得比暴力匹配快,主要优点是主串不回溯。