最近做了一道题是叫写出一个字符串的nextval数组,其实我是知道next数组的含义的,上网搜了一下很着急,因为全部都在讲怎么求nextval和next数组,却没有一个讲清楚了nextval究竟是个什么东东,大概看了下求法,然后又自己按照next数组实现了一下kmp,顿时领悟了那个nextval是什么,不过其实这个数组用处不是太大。
牛客网请的左程云老师的课听了也非常清晰,虽然听了又忘,还是自己懒惰懒于写代码,今天终于写成了。注意有个区别,我实现的kmp是寻找出现的次数,所以当匹配成功后还要继续匹配,所以我的next数组长度是(匹配字符串ms的长度+1)。is
此处next数组每个节点next[i]的含义是i节点以前(不包含)的字符串的最长公共前缀和后缀的长度,这个长度并不包含字符串本身。举个栗子:字符串 ABCDABCD,那么next[6]的值也就是ABCDAB的最长公共前缀和后缀长度,即AB,也就是2,这个匹配长度并不包含ABCDAB这个字符串本身。这里next数组的长度比匹配字符串本身要大1,即next[8]也就是整个字符串ABCDABCD的最长公共前缀和后缀长度,即ABCD,也就是4。还有特殊情况就是next[0]=-1,next[1]=0,这里next[0]=-1没有任何意义,仅供占位。
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
//求next数组
int * getNextArray(char *ms)
{
int *next = (int*)malloc(sizeof(int)*(strlen(ms)+1));
if(strlen(ms) == 1)
{
*next = -1;
return next;
}
next[0] = -1;
next[1] = 0;
int pos = 2;
int cn = 0;
while(pos <= strlen(ms))
{
if(ms[pos-1] == ms[cn])
next[pos++] = ++cn; //1
else if(cn>0)
cn = next[cn]; //2
else
next[pos++] = 0; //3
}
for(int i=0; i!=strlen(ms)+1; i++)
printf("next[%d] = %d\n", i, next[i]);
return next;
}
//要么pos增加, 要么pos-cn增加
int KMPmatch(char *ms, char *str)
{
int *next = getNextArray(ms);
int count = 0;
int msl = strlen(ms);
int strl = strlen(str);
if(msl>strl)
return 0;
int mspos = 0;
int strpos = 0;
while(strpos < strl)
{
if(ms[mspos] == str[strpos])
{
mspos++,strpos++; //4
if(mspos == msl)
count++;
if(strpos == strl)
return count;
}
else{
if(mspos == 0){
strpos++; //5
if((strl-strpos) < msl)
return count;
}
else
mspos = next[mspos]; //6
}
}
}
//要么ms数组在往前推,要么pos往前走,时间复杂度最多为O(2N);
int main()
{
char *ms = "aab";
char *str = "aaabaaacaaab";
printf("%d", KMPmatch(ms, str));
return 0;
}
第1,2,3步是求next数组的核心部分:
1.如果能够匹配就一直匹配下去,一直到超过数组长度或者不能匹配为止;
2.如果不能够匹配,且前面已经由匹配的字符串,那么我们就看已经匹配字符串自身的最长公共前缀后缀里面能不能再匹配,这里如果最后找不到匹配的cn会回到0;
3.如果前面没有匹配的,也就是当前字符和第一个字符都不相等,那只能让下一个字符和第一个字符再匹配一下;
第2步比较关键,我们举个栗子说明一下:
abcabd abcabc 令此时cn=0, pos=7,这时我们匹配,那么会直接到cn=5, pos=12,这时候发现string[cn=5]!=string[pos-1=11],即d!=c,那么我们有理由去看next[5],因为已经匹配过的abcab是相同的,他们拥有相同的前缀后缀。于是会找到cn=2去,这是发现string[2]==string[pos-1=11],于是就能确定next[12]=3,如果不匹配那么继续往前面罩,cn最终会变回0;
关于时间复杂度的严肃证明,准备翻一下算导,但是发现算导的语言风格自己很不适应根本看不下去,于是在这里重述一下左程云老师的通俗证明法。
1.求next数组,设ms串长度为M,我们可以看到pos和(pos-cn)都属于范围[0,M],
注释为1的分支 pos增加 (pos-cn)不变
注释为2的分支 pos不变 (pos-cn)增加---由于cn减少
注释为3的分支 pos增加 (pos-cn)增加
三个分支都是让pos或者(pos-cn)增加,那么循环次数肯定小于2M次,所以求next数组的时间复杂度为O(M);
2.KMP过程时间复杂度
注释4和5都是使strpos增加,而注释6的地方会让匹配字符串在str上的左边界(即strpos - mspos)往右推移(即增加),而这两个变量也都是[0,n]范围内的,所以循环次数肯定小于2N
故而时间复杂度为O(N);
最近看了libevent源码,愈发觉得数据结构和算法的重要性,
又翻看了以前收藏或者转载的博客,却发现自己又有新的收获,在我的前半生我总是不断去追逐新的东西,但是转头发现自己收获的确实不多,温故而知新,自己要加油了。