KMP算法
前置知识
后缀
后缀 是指从某个位置
真后缀 指除了
举例来说,字符串 abcabcd
的所有后缀为
{d, cd, bcd, abcd, cabcd, bcabcd, abcabcd}
,而它的真后缀为
{d, cd, bcd, abcd, cabcd, bcabcd}
。
前缀
前缀 是指从串首开始到某个位置
真前缀 指除了
举例来说,字符串 abcabcd
的所有前缀为
{a, ab, abc, abca, abcab, abcabc, abcabcd}
, 而它的真前缀为
{a, ab, abc, abca, abcab, abcabc}
。1
KMP 算法
定义
Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP 算法”,常用于在一个文本串 S 内查找一个模式串 P 的出现位置,这个算法由 Donald Knuth、Vaughan Pratt、James H. Morris 三人于 1977 年联合发表,故取这 3 人的姓氏命名此算法。
下面先直接给出 KMP 的算法流程:
- 假设现在文本串 S 匹配到 i 位置,模式串 P 匹配到 j 位置 如果 j = -1,或者当前字符匹配成功(即 S[i] == P[j]),都令 i++,j++,继续匹配下一个字符;
- 如果 j != -1,且当前字符匹配失败(即 S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串 P 相对于文本串 S 向右移动了 j - next [j] 位。
- 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的 next 值(next 数组的求解会在下文的 3.3.3 节中详细阐述),即移动的实际位数为:j - next[j],且此值大于等于 1。
很快,你也会意识到 next 数组各值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果 next [j] = k,代表 j 之前的字符串中有最大长度为 k 的相同前缀后缀。
此也意味着在某个字符失配时,该字符对应的 next 值会告诉你下一步匹配中,模式串应该跳到哪个位置(跳到 next [j] 的位置)。如果 next [j] 等于 0 或-1,则跳到模式串的开头字符,若 next [j] = k 且 k > 0,代表下次匹配跳到 j 之前的某个字符,而不是跳到开头,且具体跳过了 k 个字符。
步骤
① 寻找前缀后缀最长公共元素长度
- 对于 P = p0 p1 ...pj-1 pj,寻找模式串 P 中长度最大且相等的前缀和后缀。如果存在 p0 p1 ...pk-1 pk = pj- k pj-k+1...pj-1 pj,那么在包含 pj 的模式串中有最大长度为 k+1 的相同前缀后缀。举个例子,如果给定的模式串为“abab”,那么它的各个子串的前缀后缀的公共元素的最大长度如下表格所示: > > 比如对于字符串 aba 来说,它有长度为 1 的相同前缀后缀 a;而对于字符串 abab 来说,它有长度为 2 的相同前缀后缀 ab(相同前缀后缀的长度为 k + 1,k + 1 = 2)。
② 求 next 数组
- next 数组考虑的是除当前字符外的最长相同前缀后缀,所以通过第 ① 步骤求得各个前缀后缀的公共元素的最大长度后,只要稍作变形即可:将第 ① 步骤中求得的值整体右移一位,然后初值赋为-1,如下表格所示: > > 比如对于 aba 来说,第 3 个字符 a 之前的字符串 ab 中有长度为 0 的相同前缀后缀,所以第 3 个字符 a 对应的 next 值为 0;而对于 abab 来说,第 4 个字符 b 之前的字符串 aba 中有长度为 1 的相同前缀后缀 a,所以第 4 个字符 b 对应的 next 值为 1(相同前缀后缀的长度为 k,k = 1)。
③ 根据 next 数组进行匹配
- 匹配失配,j = next [j],模式串向右移动的位数为:j - next[j]。换言之,当模式串的后缀 pj-k pj-k+1, ..., pj-1 跟文本串 si-k si-k+1, ..., si-1 匹配成功,但 pj 跟 si 匹配失败时,因为 next[j] = k,相当于在不包含 pj 的模式串中有最大长度为 k 的相同前缀后缀,即 p0 p1 ...pk-1 = pj-k pj-k+1...pj-1,故令 j = next[j],从而让模式串右移 j - next[j] 位,使得模式串的前缀 p0 p1, ..., pk-1 对应着文本串 si-k si-k+1, ..., si-1,而后让 pk 跟 si 继续匹配。如下图所示:
综上,KMP 的 next 数组相当于告诉我们:当模式串中的某个字符跟文本串中的某个字符匹配失配时,模式串下一步应该跳到哪个位置。如模式串中在 j 处的字符跟文本串在 i 处的字符匹配失配时,下一步用 next [j] 处的字符继续跟文本串 i 处的字符匹配,相当于模式串向右移动 j - next[j] 位。
代码实现
1 | // s[]是长文本,p[]是模式串,n是s的长度,m是p的长度 |
参考
- [1] 字符串基础 - OI Wiki
- [2] 从头到尾彻底理解 KMP