下面是求next数组的板子:
void getnext(char *b)//整个过程就相当于对自己和自己用kmp算法,但是其中一个自己要在另一个自己前面一个元素进行匹配。
{
int n=strlen(b);
nx[0]=-1;
int j=0,k=-1;//其中一个自己要在另一个自己前面一个元素进行匹配。
while(j<n)
{
if(k==-1||b[j]==b[k])//元素相等则j、k都++。
{
++j;
++k;
nx[j]=k;//更新next值
}
else k=nx[k];//元素不相等k就回溯,由于j在k前面,所以此时nx[k]是已经被算出来了的。
}
}
下面是kmp算法的板子(a是主串,b是模式串):
inline int kmp(char *a,char *b)
{
int ans=0;
getnext(b);
int m=strlen(a);
int n=strlen(b);
int i=0,j=0;
while(i<m)
{
if(j==-1||b[j]==a[i])//j==-1的情况是第一个字母就不匹配。如果字母匹配i和j就同时后移
{
++i;
++j;
}
else j=nx[j];//如果匹配失败,j回溯。
if(j==n)//匹配成功就ans++,j回溯。
{
ans++;
j=nx[j];
}
}
return ans;
}
KMP算法实际上就是通过对模式串进行预处理来缩短匹配时间,时间复杂度是O(m+n),next数组记录着如果匹配失败应当将在模式串上的指针移动到模式串的哪个位置(具体的移动方式是:当主串S与模式串P失配时,j=next[j](这一步是将j回溯,使得j刚好停在最长公共前后缀的后面一位,因为前面是匹配的(公共前后缀),所以从j开始比较即可),P向右移动j - next[j](用next [j] 处的字符继续跟主串i 处的字符匹配,相当于模式串向右移动 j - next[j] 位))。从本质上讲next数组的含义是除了当前字符外,当前字符之前的字符串的最长公共前后缀的长度,next数组是用公共前后缀来实现的。
不定义为内联函数似乎调用函数的时间开销很大,所以在多次调用函数时要把函数定义为内联函数,这样就减少了因调用函数而产生的时间开销。
b站有个视频讲的很清楚:
链接
注:如果想要求两个字符串的最长公共前后缀(比如sample和please的最长公共前后缀是ple,其长度为3),只需要将两个字符串按一定顺序拼接后求拼接后字符串的next数组,求出的next数组的最后一个数字就是答案。
想要快一点可以利用下面的kmp函数的返回值,其返回值就是答案。
inline int kmp(char *s,char *t)
{
int len=strlen(s);
int n=strlen(t);
int i=max(0,len-n),j=0;
getnext(t);
for(i;i<len;)
{
while(s[i]!=t[j])
{
if(nx[j]==0)
{
j=0;
break;
}
else j=nx[j];
}
if(s[i]==t[j])
{
j++;
}
i++;
}
return j;
}
一些用得频繁的与字符串操作有关的函数:
1.memcpy函数的速度要比strcpy快,它比strcpy多了一个第三个参数用来规定要复制的字节数,而且要注意memcpy不会在最后加上结束符\0,strcpy会在最后加\0。
2.strcat函数的速度较慢,因为每次都要遍历到\0后再进行拼接,想提高速度可以手写。
提醒一句:没有返回值的函数类型一定要写void,不然交到oj上会RE,而且平时也可能出现一些问题,luoguP3375是血的教训= =、
2021/4/16
今天上数据结构课学了next数组的优化方式,有效优化了某些特殊情况下kmp的时间复杂度,就是在原next数组的基础上,如果一个元素等于它回溯值处的元素,那么这个元素的回溯值就是它回溯值处元素的回溯值。(证明可以自己手动画一下,其实也不是很难想,因为当前元素不匹配的话,如果回溯值处的元素与它相等肯定也不匹配啊。)
下面是代码:
void getnext(char *b)
{
int n=strlen(b);
nx[0]=-1;
int j=0,k=-1;
while(j<n)
{
if(k==-1||b[j]==b[k])
{
++j;
++k;
if(b[j]!=b[k])//如果一个元素不等于它回溯值处的元素
nx[j]=k;//那么它的回溯值该是啥还是啥
else nx[j]=nx[k];//如果一个元素等于它回溯值处的元素,那么这个元素的回溯值就是它回溯值处元素的回溯值
}
else k=nx[k];
}
}