先写这么多,有时间了整理思路好好写一下自己的看法。
我这里的代码与标准的哪些next数组代码不一样,精简版只有5行代码量,浓缩的都是精华,但我认为这种开始更便于理解,那些精简的代码主要是将next数组变换了一下,定义也不一样,我这里是next [ i ] 表示的长度为 i+1 的字符串公共前后缀长度,其他的一般是把我这种结果的 next 数组向右平移了一位对齐,next [ 0 ] = -1,next [ i ] = len的意思是长度为 i 的字符串公共前后缀长度为len,这样可以对代码做出改进,但我们暂时不讨论这么多,后续再优化
代码如下,代码里对next数组求法做了一些注释,但关键还是没有说明,有些人一直卡在不知道什么地方,绕进去出不来了,可能只要点一下某一步就豁然了。有几点我说一下,我们可以观察到求next数组和kmp整体代码结构相似极高,这里多思考一下,其实求next数组的方法就是在已知了第一个元素没有前后缀next值就是0,然后利用这个求第二个,就这样一步一步推。我们将模式串复制为两个,用两个指针分别指向他们的各个元素从头比较,如果相同,指针各加一,此时next值就是前一个元素的next值加 1,这个很多博客都说明了我就不再赘述;关键是不同的时候,一旦不同,那么是否代表了没有公众前后缀了呢?此时next值难道就为0了吗?这时候就是关键了,我们就得回想到KMP这个算法的思想!!!就是在回退指针时不回退所有!!!有没有可能有部分相同的地方(到这里很多人可能被绕进去了,不是说已经比较不一样了吗,怎么又来个再找部分一样的),我这里举个例子,ab ab ab ac ab ab ab ab ,这是一个字符串abababacabababab,在这里,对这个字符串整体来说最大公共前后缀是abababa,然后到那个c与最后这个比较的时候,不一样了,那么代码里的指针回退到前一个元素的next值处(精简版是直接等于该元素next值),此时就是把眼光看回ababab这个公众前后缀!!!!它自身里面是否还有公共前后缀!!!比如这里,先看abababac里的aba b aba c,(我把c隔远了先不看因为就是到他断了,我把眼光看到前面已经配对的)我们发现aba 与 aba这一对公共前后缀,再者,我们是不是已经知道这段字符串是和abababab里的abababa是匹配的!那后面这对也是有公共前后缀aba aba,这四个就是等价的!!此时就是要把最后一个字符b移到前面来与aba这段比较短的公共前后缀的下个字符再比较!!这就是关键!!!而他前一个元素的next值就是表示这串公共前后缀多长,比如这里就是3,再由于我们数组都是从0开始计数,所以当这个next值作为数组下标的时候就是刚好指在了这串字符的下一个位置!!!也就是第四个元素的位置,然后从这里开始比较!!!比如这里就是aba b ,最后一个b与这个b比较,刚好一样。这里非常巧妙,所以才会有那行最让人莫名其妙的代码,暂时就说这么多,改进代码稍后再看,初步理解kmp的next求解过程即可
看图
#include<stdio.h>
#define MAXSIZE 10
typedef struct
{
char *ch;
int length;
}String;
void GetNext(String P, int next[])//此处的next[i]表示的长度为 i+1 的字符串公共前后缀长度
{//求next数组的过程本质上就是利用kmp思想将模式串与自己比较来递推next数组,可以发现此代码和kmp代码形式即为相似
int i = 0, j = 1;//两个指针,想象将模式串复制为两个,i指向其中一个模式串的第一个元素(就是把他当作主串),j指向另一个模式串(当成真正的模式串与前述的那个比较,我们求next数组主要就是看这个)的第二个元素(因为我们默认了第一个元素的nextz值就是0,所以从第二个开始)
next[0] = 0;//默认第一个元素无前后缀即为0
while(j < P.length)//循环条件
{
if(P.ch[i] == P.ch[j])//如果两指针比较相同
{
next[j++] = ++i;//这里有好几步,分开看就是先next[j] = i+1; 然后i++;j++; 再进入下一对字符比较
}
else//如果不相同,要做的操作就是回退指针i,先别多想啥意思,继续看
{
if(i > 0)//防止回退过头了
{
i = next[i - 1];//这里是关键,意思就是i指针回退到前一个元素的next值指向的地方,很多人对这里不懂,暂时搁置下
}
else
{
next[j] = 0;//这里还可以这样写 next[j] = i;其实此时i就是0, 如果i一直回退到第一个next值也就是next[0],那就代表没有任何公共前后缀,那此时j指向的元素next即使0;如果懵了不要紧,先看下去
j++;//此时我们知道i回到了第一个元素,再j还没++前,j指向的next值是0,此时就得重新开始比较,前面的各个元素公共前后缀都没啥影响了,就是断了,那此处j就指向下一个新元素,两对再从头开始
}
}
}
}
int KMP(String S, String P)
{
int i = 0, j = 0;
int next[MAXSIZE];
GetNext(P, next);
while(i < S.length && j < P.length)
{
if(S.ch[i] == P.ch[j])
{
i++;
j++;
}
else
{
if(j > 0)
{
j = next[j - 1];
}
else
{
i++;
}
}
}
if(j == P.length)
return i - j + 1;
else
return 0;
}
int main()
{
String P;
P.length = 6;
P.ch = "ababca";
String S;
S.length = 14;
S.ch = "abaababdababca";
int next[MAXSIZE];
GetNext(P, next);
for(int i = 0; i < 6; i++)
{
printf("next[%d]= %d ", i, next[i]);
}
printf("\n");
if(!KMP(S, P))
{
printf("no");
}
else
{
printf("pos = %d ", KMP(S, P));
}
}