初级KMP算法详解
所谓KMP算法,就是查找字符串中给定子串的位置,没有则返回特定数值。先介绍几个概念:
前缀
就是头部字串,比如China的前缀有C,Ch,Chi,Chin(不算最后一个字符)
后缀
就是尾部子串,China的后缀就是a,na,ina,hina(不算第一个字符)
部分匹配值
一个字符串的前缀集合和后缀集合的交集中,最长那个子串的长度
通常形如abxxab这样的部分匹配值就是2,因为前缀的ab和后缀的ab是共同和最长的(xx没有实际意义,但里面不应该有更长的)。
PM表
把子串的部分匹配值挨个写出来(懒得写了)随便写个例子:
str:abcac
a-0,b-0,c-0,a-1,c-0
移动位数 = 已匹配的字符数 - 对应的部分匹配值
这里用KMP算法为了减少匹配的次数,通过移动位数的方式
KMP算法中的next数组
这里首先声明一下KMP算法子串和主串的指针是如何移动的,以1为初始:
- 如果子串的指针指向0,也就是初始之前,子串和主串的指针就要同时往下一个移动,匹配子串的第一个字符和主串当前的字符;
- 如果子串指针指向的字符等于主串指针指向的字符,那么指针接着移动匹配下一个。
- 如果没匹配成功,则子串的指针按照对应的next数组进行跳转
注意:无论是PM表还是next数组都是对应子串的,也就是看子串的部分匹配性
next数组在这里就代表着子串接下来要跳转的指针和当前的i匹配,所以这里强调:
next[j]就是在没匹配成功的时候,子串指针j跳转的目的地
next数组的实现
知道了next数组的意义,那么就剩实现next数组了,我这里提供一个理解的方法:
以1作为开始,也就是j=1是子串指针移到子串第一位时:
- 首先next[1]=0,代表我第一个失败我就接着匹配主串的下一个看看。比对第一个失败时j(子串指针)跳转到0,如果j跳转到0,主串和j都执行++,主串的指针就会移动!
- 然后next[n],n>1的话都代表第一个匹配成功了,如果没有特殊情况next[n]=1,代表的是这个对不上我用子串的第一个看看对的上不(重新匹配),要是对不上就再移动主串。
- 接下来就是特殊情况,是我自己总结的方便理解的版本,先说结论:
假如n指针前面有那种部分匹配的串,形如abxxab那种,next[n]就等于部分匹配值+1
加个next数组简单的分析
再详细解释一下:n指针前面就是去掉n这个值看子串有没有长那样的。
就用ababa分析一下:
- 首先最开始的a是0,next[1] = 0
- ab去掉b看就剩a了,所以没什么特殊的。next[2] = 1
- aba去掉a看只剩ab了,也没啥特殊能匹配的,next[3] = 0
- abab去掉最后一个字符看只剩aba了,aba的部分匹配值就是1,next[4] = 2,假如4对不上我还能去2看看能不能对上,2再对不上我就只能接着去next[2]看看了
- ababa去掉最后一个字符看只剩abab,部分匹配值是2,next[5] = 3,5对不上我去3看看,放到字符串里也是很合理的,假如出现了ababc,这第二个ab也有可能对着子串的第一个ab,我也得去3看看不是。
nextval数组的构造
相比于字符next跳转增加逻辑判断,就比如ababa,假如第五个不是a,我要跳转next[5],但其实3我也一定匹配不成功,因为3检查的也还是a,那我肯定不是a啊,所以我一定会接着跳转next到[1]检查是不是a,也一定会跳转到0。所以不如直接把next[5]赋成0,这就是nextval数组,装的都是这种经过判断的。