鉴于有很多人都在自己的博文中介绍了KMP的思想和主体函数,我就不赘述之了。这里我只为自己学习KMP算法过程中遇到的难题做一下笔记,是关于算法中next数组的求解的,方便自己以后翻阅,也方便有相同需求的人浏览
建议大家在浏览的时候,自己可以在纸上画着图,更容易理解
- 第一个难点在于:理解next数组
假设我们的模式串为 P
我们知道,KMP算法中,next的定义是:
next [ j ] = -1 ,若 j = 0
next [ j ] = max [ k ] ,其中 P[ 0 ... k-1 ] = P[ j-k ... j-1 ]
next [ j ] = 0,其他
这定义是什么意思呢?
先来分析一下 next [ j ] 是什么意思,首先 j 表示在匹配过程中,P[ j ] 和主串S的 S[ i ] 不相等,既然不相等,根据我们的KMP算法,我们要找到P中的某一个位置 k ,回溯我们的P串的指针到 k 处。
而 k 的含义有是什么呢?它指代P中的一个位置,满足从P的头连续到位置 k - 1 的这个子串,我们设为 p1,要和 j 之前的某个连续子串p2相等。注意,p2一定要紧挨在 P[ j ] 后面的。
k 的意义在于,只要我们知道了一个k ,那就表示我们可以不重复去匹配 P[ 0 ... k-1 ](想想看BF算法,它每次都要从头开始匹配),而可以直接从k开始匹配主串中的S[ i ]了。
而我们当然希望k越大越好啦,因为这样我们就能够匹配更少的子串
所以,这个next[ j ] 的求解,就是对应于每个 j ,都求解它的 k。
- 明白了next的含义后,第二个难点就是:怎么求解next数组
- P[ j ] = P [ k ],很显然,这说明我们在 j + 1 处发生不匹配时,P[ 0 ... k ] = P[ j + 1 - k ... j ] (注意我们上面的前提),所以 next [ j + 1 ] = next [ j ] + 1 = k + 1
- P[ j ] != P[ k ] ,这个情况比较复杂,但是只要细细思考,也是可以想通的:我们必须移动k,而这个移动操作是 k = next[ k ],然后通过循环来解决next[ j + 1 ]
回顾我们的思路,我们要找的 k 要满足,前k个字符的子串要和位置 j 前的k个字符的子串相等
因为P[ j ] != P[ k ] ,所以我们没办法直接使用 k 了,只能从P的头开始找另一个基准 k ,满足上面的条件 ” 前k个字符的子串要和位置 j 前的k个字符的子串相等 “ 。
难道我们真的要从P的头开始一个个匹配吗?不!我们要利用我们的前提条件!至少我们要跳过一些字符!
大家想象 next [ k ] 的含义是什么?对 k 找到一个位置 x ,也满足从0 到 x 的子串 要和 k 前长度为 x 的子串相等,这样我们不就可以跳过 x 个字符了?有了这 x 个字符的基础,我们只要再找到 P [ j ] = P[ x ] 就行了。为什么?因为我们现在在求 next [ j + 1 ] 啊!如果 找到了x后发现不满足P [ j ] = P[ x ]怎么办?那就重复操作,再找 y = next [ x ] ,直到找到为止,这就是为什么我们要 k = next [ k ] 然后递归了
实现的代码如下:
void getNext(char *p, int *next)
{
int i = 0, j = 0;
int k = -1;
next[0] = k //根据next数组的定义
while(j < strlen(p) - 1)
{
if( j == -1 || p[k] = p[j] )
{
j++;
k++;
next[j] = k;
}
else
{
k = next[k];
}
}
}