传统的朴素模式匹配算法
由于主串指针i回溯,且每次只前进一个距离,没有减少不必要的匹配过程。故效率非常低。
KMP算法
与朴素模式匹配算法不同,kmp巧妙的利用了当前c字符失配而c字符前面的字符都成功匹配
的信息,因此可跳过某些不必要的匹配。kmp将关注点落在了模式串t的有效信息提取上,与主串s
无关,故消除了主串s的回溯
1.kmp的重点就落在对模式串t的有效信息提取上即最长公共前后缀next数组
next数组是kmp算法的核心
next数组用以保存当前字符c之前子串的最长公共前后缀的信息
- 手算next数组
模式串t:g o o g l e
前缀集(不含最后字符) | 后缀集(不含开始字符) | 交集元素个数 | |
---|---|---|---|
g | 空集 | 空集 | 0 |
go | {g} | {o} | 0 |
goo | {g,go} | {o,oo} | 0 |
goog | {g,go,goo} | {g,og,oog} | 1 |
googl | {g,go,goo,goog} | {l,gl,ogl.oogl} | 0 |
{g,go,goo,goog,googl} | {e,le,gle,ogle,oogle} | 0 |
模式串t | g | o | o | g | l | e |
---|---|---|---|---|---|---|
next数组 | 0 | 0 | 0 | 1 | 0 | 0 |
利用next数组匹配
如上图当在字符 l 处发生匹配失败时,可知前面所有的字符已匹配成功。此时我们可能会想如果把模式串向右移使第一个字符g对齐匹配失败的前一个字符就好了。
没错这就是kmp算法的核心利用最长公共前后缀减少不必要的匹配过程
那么问题来了,将模式串向右移,移多少呢?这时候我们之前提到的提取有效信息next数组就发挥作用了。
我们需要将失配之前的最长公共前缀部分移动到后缀部分,也就是如下图这样。
移动公式:
move = 指针j之前匹配成功个数 - next[j-1] //将前缀移动到后缀处
= (j-1) - next[j-1]
将比较指针j回退到
j = j-move
= j - ((j-1)-next[j-1]) = next[j-1]+1
kmp模式串利用next数组匹配的核心 (j如何回退,回退到哪)
至此最重要的信息我们知道了,当字符c匹配失败后比较指针j回退到j = next[j-1]+1
即与失配指针j之前后缀字符相同的前缀后一个字符
也许我