简单易懂的KMP算法
也许更好的阅读体验
隔了一年半,很多算法都忘了,复习一遍
KMP的作用
学一个算法,首先要知道这个算法是用来做什么的
KMP的作用是字符串匹配,即在一个字符串(t)里查找某特定字符串(s)的位置以及次数
比如\(t="abcabcdabca",s="abcabd"\)
当然,最简单的方法就是枚举法,以t的从左到右第n个字母为起点看长度为4的字符串是不是和s一样
而KMP则是优化这个方法
KMP难点在理解,代码本身十分简单,因此我重点放在理解上
KMP匹配原理与实现
举个明显的例子,当我们匹配上述两个字符串到这个情况时
发现不行,如果让你来用肉眼进行下一次匹配是不是会这样做
因为我们一眼看过去就知道前面有好几次没有必要匹配,并且abc的ab和后面的abd的ab是一样的可以不用再匹配
但是电脑不能一眼看过去,而KMP就是实现上述过程,即省略不必要的匹配
为了更明显的表示,我用这样的一个图来解释
我们要在上面这个条形柱中找下面这个条形柱,当匹配到这种情况时,发现紫色部分和棕色部分不相符,我们考虑重新匹配
而下面这个条形柱的最前面的粉色区和后面的粉色区是一样的,因此可以这样移动
kmp算法就是实现这样的过程,而做到这样过程的关键就是记录下匹配到此位置时失败了接下来前移到哪里开始再次匹配,通常用nxt数组来表示
简单来说nxt[i]表示以i-1为末尾的后缀和以1开始的前缀的最长的相同的一段,因为我们考虑的是匹配到第i个字符时失配了该从哪再次匹配,因此应是以i-1为末尾(如果这种说法让你更迷了请忽略)
明白了nxt数组的意思后构造nxt数组就不是很难了,上代码
int nxt[maxn];
char s[maxn];//s数组从s[1]开始
void Getnxt ()
{
//若s数组从s[0]开始,应设置j=0,k=-1,nxt[0]=-1,!k改为k==-1
int j=1,k=0,slen=strlen(s+1);
while (j<=slen){
if (!k||s[j]==s[k]) nxt[++j]=++k;
else k=nxt[k];
}
}
根据nxt数组,不难想出匹配函数的打法
int match (char *t)
{
int res=0,i=1,k=1,tlen=strlen(t+1);//s从s[0]开始自行修改
while (i<=tlen){
if (!k||s[k]==t[i]) ++k,++i;
else k=nxt[k];
if (k>slen) ++res,k=nxt[k];
}
return res;
}
再解释下kmp算法为什么可以统计出出现次数,可能有些人会奇怪,跳着匹配不会漏掉中间的吗,很简单的道理,因为nxt数组一直到slen+1