字符串匹配有很多算法,最简单的算法就是从模式P的开头(j=0)和主串S的某个位置开始进行比较(i),如果相等(p[j] == s[i]),则比较模式串的下一个位置和主串的下一个位置(++j;++i), 如果不相等(p[j] != s[i]),则要发生回溯,从主串的位置i-j重新开始匹配,这样速度会收到影响( i = i-j+1; j=0 )。
KMP算法的优势即在于避免了回溯,当模式串j和主串i位置的字符不匹配时,查询一个数组next,从模式串位置p[ next[j] ]开始和主串的i位置开始匹配,这样主串就没有回溯,速度也就加快了,那么如何计算next数组呢?
首先对next数组的功能进行介绍,next数组的目的是对模式串的每一个位置i存储一个值j,当模式串中位置j的字符和主串位置i字符不匹配时,应该从模式串中的另一个位置next[j]重新和主串的当前位置i开始比较,next数组的计算方法可以保证,next[j] < j, 所以从另外一个意义解释就是寻找一个模式前缀p[0, ..., next[j]-1 ]和模式后缀p[ i-next[j], ..., i -1 ]相匹配。因为模式p[j]和主串s[i]不匹配,意味着模式串p[0, .., j-1]和主串s[i-j, ..., i-1]是匹配的,改变j,使得j=next[j], 并从p[next[j]]这个位置和主串i开始匹配,必然有上面的要求。而且可以看到next数据的计算只跟模式串有关。那么,为什么这样做是可以的呢?会不会漏掉可能的匹配呢?答案当然是不会的。
下面开始解释这个问题,当模式串位置j和主串的当前位置i不匹配时,很明显模式串j之前的字符串(即p[0 ... i-1])和主串的当前位置之前的字符串(即m[ i-j ... i-1)是匹配的,假如在某个情况下,存在一个模式子串p[ k, .., t] ( k > j-i+1 )和主串p[ u, .., i]匹配,那么这就是相当于模式串p[ 0 ... i-1 ]存在一个前缀和模式串的一个后缀匹配,这跟计算next数组的初始想法完全是一样的,可见不会丢掉任何可能的字符串匹配。
那么如何计算next数组呢?next数组的内容可以递推的求解,如果我们已经知道next[i]数组的值,那么就可以求得next[i+1]的值。因为求解next[i+1]就是求解一个前缀p[0, ..., next[i+1]-1],使得p[0, ..., next[i+1]-1]和p[i+1-next[i+1], ..., i]匹配。而且我们现在已经知道p[0, ..., next[i]-1]和p[i+1-next[i], ..., i]匹配。如果此时p[i] == p[next[i]],那么next[i+1]=next[i]+1,此时即可满足条件。如果p[i] != p[next[i]],问题即变成求解next[next[i]]。
有如下代码变量i:用于指示模式串需要计算的位置,j:用于存储前一个的next值。代码如下:
int i = 0, j = -1;
if( -1 == j || t[i] == t[j] ){
++i; ++j;
next[i] = j;
} else
j = next[j];
}