用途
改(抄)自某乎
文本串:我我是我是废我是废物废物是我
模式串:我是废物
开始匹配:
我我是我是废我是废物废物是我
我
我我
欧豁,匹配失败了。那么从当前匹配字符的下一个字符开始匹配:
我我是我是废我是废物废物是我
一我
一我是
一我是我
欧豁,又匹配失败了。继续从下一个字符开始匹配。。。等等?你是不是虎啊,下一个明显匹配不上,应该跳过两个后再匹配!好好好,你声大你有理,我移两个我移两个:
我我是我是废我是废物废物是我
一一一我
一一一我是
一一一我是废
一一一我是废我
又对不上了,这。。这次再向后移动几个(小声)?这明显三个啊!
我我是我是废我是废物废物是我
一一一一一一我
一一一一一一我是
一一一一一一我是废
一一一一一一我是废物
匹配上了,我是废物!!!
例子结束了,思考一个问题,当匹配失败时,我们如何知道的需要向后移动的最优次数?这个就是 KMP 算法中的 Next 数组
帮我们做到的。使得当每次匹配失败时,不用傻傻的向后移动一下(暴力解法)。
Next 数组
前缀
在一个文本串中,不包含尾字母,从左至右的可以组成的所有序列。
例:a b c d
前缀有:
- a
- a b
- a b c
后缀
在一个文本串中,不包含首字母,从左至右的可以组成的所有序列。
例:a b c d
后缀有:
- d
- c d
- b c d
注意,后缀也是从左至右的。如:d、dc、dcb 这样的并不是后缀。
特殊例子
例:a(可以是任意个单字符)
因为文本串中只有一个字符,所以它既没有前缀,也没有后缀。
最长相等前后缀
例:a b a b a
前缀:
- a
- a b
- a b a
- a b a b
后缀:
- a
- b a
- a b a
- b a b a
最长相等前后缀:a b a,即 a b a b a 这个字符串的最长相等前后缀为 a b a。
Next 数组的内容
介绍了前后缀以及最长相等前后缀的概念后,就可以来说一下 Next 数组了,它是 KMP 算法的核心。Next 数组中存放的,就是模式串(需要匹配的字符串)
自身,以及所有子串的最长相等前后缀的长度
。
上栗子,模式串:a a b a a
模式串的 Next 数组,方便理解,先给出表格版:
索引 | 代表字符串 | 最长相等前后缀长度(数组值) |
---|---|---|
0 | a | 0 |
1 | aa | 1 |
2 | aab | 0 |
3 | aaba | 1 |
4 | aabaa | 2 |
简版:
"a a b a a"
[0 1 0 1 2]
Next 数组的应用
现在我们已经得到了 Next 数组,可是如何进行应用呢?如何用它使得每次可以移动正确的次数,减少匹配次数?
上栗子:
文本串:a a b a a b a a c
模式串:a a b a a c
模式串的 Next 数组:[0 1 0 1 2 0]
流程:若当前模式串字符匹配失败时,找到前一个字符在 Next 数组中的最长相等前后缀长度值。如果将这个长度作为索引,正好可以指向由首字符到前一个字符组成的字符串的最长相等前缀的下一个字符
。继续使用该字符进行匹配。若又匹配失败,回到流程的开头,反复如此
。可以发现当具有相等前后缀时,一直在移动模式串。
比如栗子中,当用模式串索引为 5 的字符 c 匹配文本串索引为 5 的字符 b 时匹配失败,那么就看 c 的前一个字符 a 在 Next 数组中的值,为 2。那么取模式串中索引为 2 的字符 b 继续和文本串中索引为 5 的字符 b 继续匹配,直到最后匹配成功。
Next 数组应用时的疑惑
我晓得,上面的流程可能有一些难以理解,因为有一些细节需要自己慢慢思考清楚:
- 模式串中,前一个字符和和当前匹配失败的字符有什么关系,为什么要获取它在 Next 数组中的最长相等前后缀的长度值?
- 取到值后,为什么就可以直接作为索引值?
第一个问题:
- 在当前匹配的模式串字符前的字符,为我们已经成功匹配的字符
- 若前面成功匹配的字符存有相等的前后缀,思考一下,前、后缀的内容是不是具有相等的性质
- 因为前、后缀内容的相等,是不是就可以直接用最长相等前缀的下一个字符来匹配当前匹配失败的文本串字符,从而跳过最长相等后缀内容的匹配
- 从另一种角度上来说,如果模式串具有相等前后缀,当匹配相等前缀的某个字符时,实际上也同时匹配了后缀中的该相等字符
- 总结一下:由于前后缀具有相等性质,从而使得可以避免一部分字符的重复匹配
第二个问题:
先列出一些可以一眼看出最长相等前后缀的字符串:
a
a a
a b a
a b c a b
a b c d a b c
是否发现一个规律:这些相等的前后缀都包含着首尾字符,也就是说前缀是从 0 索引开始,后缀以 legnth - 1 索引结束。那么就完全可以根据后缀的长度,推出前缀后的第一个字符的索引,即后缀的长度,即前缀的长度,即最长相等前后缀的长度,即 Next 数组中存储的最长相等前后缀的长度。
算法复杂度
介绍完 Next 数组存储的内容以及应用,实际上 KMP 算法基本就说完了,最后来分析一下该算法的时间复杂度。
最坏的情况下:O(m + n),m 为模式串的长度,n 为文本串的长度。
栗:
文本串:a a c
模式串:a a a
模式串的 Next 数组:[0, 1, 2]
由于匹配到字符 c 时匹配失败,根据模式串的 Next 数组使得模式串每次向前移动一下。
最好情况下:O(m),即从头部开始匹配,直接匹配成功了。
(下一篇:会实现 KMP 算法)