通俗易懂KMP模式匹配法及代码实现

最近碰到一道KMP字符串匹配题目,首先觉得用自己的笨办法应该也能做,子字符串分别于母字符串匹配,若匹配不成功右移一位,这样做对于简单字符串可以在规定的时间内完成,但是对于稍复杂字符串则匹配时间过长,超出规定的运行时间。于是学习了KMP模式匹配法。

什么是KMP模式匹配

KMP模式匹配是一种高效的字符串匹配方法。它最大的特点就是充分利用的模式串(也就是子串)的隐含内在特点,匹配过程不再像传统方法那样”匹配失败右移一位“,而是“科学性”的移位匹配,从而大大减少了不必要的匹配工作量。KMP匹配与传统字符串匹配法的差别可由下图形象的看出:
通俗易懂KMP模式匹配法及代码实现_第1张图片
当我们匹配上图两个字符串时,在子串第四个字符出现匹配不成功,传功方法的下一步匹配是下面这样的:

通俗易懂KMP模式匹配法及代码实现_第2张图片
而KMP模式匹配法的下一步是这样进行的:
通俗易懂KMP模式匹配法及代码实现_第3张图片
由上图可以看出,KMP模式匹配法直接跳过了B跟C来到了与其首字符匹配的位置,高效而准确。

next[]数组

前面介绍了KMP模式匹配法其高效的原因就是充分利用了模式串本身存在的特性。这个特性归纳成为KMP算法的next数组,也是KMP算法的核心。
next数组长度与模式串长度相同,每个数组元素代表在模式串中在该位置以前的字符串的前后缀字符相同的长度。举例来说,上面的ABCDADE模式串中,next[4]=0,next[5]=1。因为next[4]是计算ABCD字符串中前后缀相同长度,其长度为0;而next[5]则是计算ABCDA的前后缀长度,可以看出是1.
当然啦,不能全部用人工来观察其前后缀长度,而是要用科学方法。这也就是KMP算法的核心——next数组的求取。

next[]数组是如何来的

next数组的求取方法简单说分为两步:
1)递归求取各子串前后缀相同的长度组成的一个数组。
2)将该数组第一个元素置为-1,其他元素设为前一个元素值即,p[j]=p[j-1]
由此,KMP核心转化为求取模式串的各个子串的前后缀相同的长度。

子串前后缀相同的长度值计算

对于模式串P的前j+1个字符,设前j个字符的前后缀相同的长度为k,最后求得的前后缀相同的长度数组记为S[],则:
1)求取递归的前两项:
S[0]肯定为0,因为它长度一共为1,不存在前后缀字符;
S[1] = P[0] == P[1] ? 1: 0;
2)若P[j] == P[k],则S[j] = next[j+1] = k+1;
3)若P[j] != P[k],此时令k=next[k]=S[k-1],若P[j] ==P[k],则S[j] = k+1;若一直不相等,直到k=0还不相等,则S[j] = 0;

个人对于前后缀长度算法的理解

光看上面的一对公式,大部分人会一头雾水,为啥这么算,还非要死记硬背下来才行吗?下面是我对上面计算方法的理解,希望可以帮助初学者理解掌握。
首先上述计算方法的核心是k值,要深刻理解这个k值。k值就是模式串的前j个字符所具有的最大前后缀相同的长度值。也就是代表该子字符串的前k个字符与后k个字符相同。
那么,在求取S[j]时2时,S[j]就会优先与其签名字符串的第k+1个字符相比较,映射到数组中就是字符P[k],若结果相同,则前后缀相同长度自然而然就会增加1;若P[k]与P[j]不相同,此时就应该回退查找更短的字符串,则先求取k前面的字符串的最大相同前后缀长度,然后判断在该字符串相同前后缀的下一个字符,是否与P[j]相同,若不相同则一直如此循环,直到k=0,也就是第一个字符是否与P[j]当等,若相等,S[j]=S[k]=S[0]+1=1,若不相等,则为0。

参考示例

模式串P为 ABAD
1)S[0] = 0;S[1] = 0;
2)求S[2]:k = 0,P[2] = = P[0],所以S[2] = S[1] + 1 = k + 1 = 1(S数组元素的值就是k值)
3)求S[3]:k=1,P[3] != P [1],继续 k = S[k-1] = S [0] = 0,P[k] = A,也与P[3]不同,k已经为0,结束递归,S[3] = 0。
综上,得到前后缀相同长度数组S为:
S 0 0 1 0
则,next数组根据前面方法可得出:
next -1 0 0 1

代码实现

求模式串的next数组

通俗易懂KMP模式匹配法及代码实现_第4张图片

根据next数组匹配字符串

注意:下面结果返回的是母串的第几个与子串相匹配,而不是索引值。
由以下代码也可以看出,KMP算法在遇到数据不匹配时,母串的索引i会根据next数组进行跳动,而不是盲目后移一位。这样便高效许多。
通俗易懂KMP模式匹配法及代码实现_第5张图片

KMP算法的优化

当模式串P[i] ==P[k]时,那么next[i] =next[k],i

结束

以上是一些个人理解,有不准确的地方还希望大家多多指导,互相交流,希望对大部分初学者有所帮助。

你可能感兴趣的:(C++)