KMP算法
一种由Knuth(D.E.Knuth)、Morris(J.H.Morris)和Pratt(V.R.Pratt)三人设计的线性时间字符串匹配算法。这个算法不用计算变迁函数δ,匹配时间为Θ(n),只用到辅助函数π[1,m],它是在Θ(m)时间内,根据模式预先计算出来的。数组π使得我们可以按需要,“现场”有效的计算(在平摊意义上来说)变迁函数δ。粗略地说,对任意状态q=0,1,…,m和任意字符a∈Σ,π[q]的值包含了与a无关但在计算δ(q,a)时需要的信息。由于数组π只有m个元素,而δ有Θ(m∣Σ∣)个值,所以通过预先计算π而不是δ,使得时间减少了一个Σ因子。[1]
KMP算法是通过分析子串,预先计算每个位置发生不匹配的时候,所需GOTO的下一个比较位置,整理出来一个next数组,然后在上面的算法中使用。
KMP算法的讲解
当我们分析一个子串时,例如:abcabcddes. 需要分析一下,每个字符x前面最多有多少个连续的字符和字符串从初始位置开始的字符匹配。然后+1就行了(别忘了,我们的字符串都是从索引1开始的)当然,不要相同位置自己匹配,默认第一个字符的匹配数是0。
定义
设字符串为 x1x2x3...xn ,其中x1,x2,x3,... xi,... xn均是字符,设ai为字符xi对应的整数。则a=m,当且仅当满足如下条件:字符串x1x2...xm equals 字符串x(i-m+1)...xi-1 xi 并且x1x2...xm x(m+1) unequals x(i-m) x(i-m+1)...xi-1 xi。
假设我们有个模式串为“abdabcde”存于数组t,我们要求的就是模式串的next值,见下表所示:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
t[i] | a | b | d | a | b | c | d | e |
next[i] | -1 | 0 | 0 | 0 | 1 | 2 | 0 | 0 |
求模式t的next[i](称为失效函数)的公式如下:
next[i] =
( 上面的公式中非t字母和数字组成的为数组下标)
应该如何理解next数组呢?在匹配过程中,如果出现不匹配的情况(当前模式串不匹配字符假定为t[i]),它所对应的next[i]的数值为接下来要匹配的模式串的字符的索引;也就是说,出现不匹配的情况时,模式串的索引指针要回溯到中next[i]所对应的位置,而主串的索引指针保持不变。
特别的,next数组中的next[0]和next[1]的取值是固定的,为了标识出首字母,需要假定next[0]为-1(取为-1是考虑到C语言中的数组索引以0开始)。在实现的时候,要实现公式中情况的处理需要些技巧,下面给出具体的实现:
# include<stdio.h>
#include<stdlib.h>
typedefstructQString{
char * cs;
int len;
}String;
voidGetNext(Strings , int next []){
int len = s . len;
int i = 0 ;
int k = - 1 ;
next [ 0 ] = - 1 ;
while (i < len - 1 ){
if (k ==- 1 || s . cs[i] == s . cs[k]){
i ++ ;
k ++ ;
next [i] = k;
} else {
k = next [k];
}
}
}
int KMPIndex(Strings , Stringm){
int next [m . len] , i = 0 , j = 0 ;
int k;
GetNext(m , next );
while (i < s . len && j < m . len){
if (j ==- 1 || s . cs[i] == m . cs[j]){
i ++ ;
j ++ ;
} else {
j = next [j];
}
}
if (j >= m . len) return i - m . len;
else return - 1 ;
}
KMP 算法也有需要改进的地方。对于模式串“aaaadd”在匹配时(假定被匹配串为“aaadddd”),可以看到,在匹配到索引3时,主串字符为“d”,模式串字符为“a”,如果按照上面的做法,这时模式串只会回溯一个索引,由于仍不匹配,模式串还会回溯一个索引,直到索引位置到了首字符,主串的索引指针才会前进一位,这样就会浪费一些不必要的比较时间。出现这种情况的原因是模式串中位置i的字符与next[i]对应的字符相同,需要修正next[i]为next[i]对应的字符的索引。下面列出“aaaadd”修正的nextval数组的内容:
i | 0 | 1 | 2 | 3 | 4 | 5 |
t[i] | a | a | a | a | d | d |
next[i] | -1 | 0 | 1 | 2 | 3 | 0 |
nextval[i] | -1 | -1 | -1 | -1 | 0 | 0 |
修正函数如下:
voidGetNextval(Strings , int nextval[]){
int len = s . len , i = 0 , k = - 1 ;
nextval[ 0 ] = - 1 ;
while (i < len - 1 ){
if (k ==- 1 || s . cs[i] == s . cs[k]){
i ++ ;
k ++ ;
if (s . cs[i] != s . cs[k]){
nextval[i] = k;
} else nextval[i] = nextval[k];
} else {
k = nextval[k];
}
}
}