KMP 算法是用来求模式字符串 $p$ 是否在某个查询字符串 $s$ 中的。设 $len(p) = m, len(s) = n$,则此算法的复杂度为 $O(m+n)$。
KMP 算法并不完全是大部分编程语言的默认匹配算法。由于它需要初始化一个 next 数组(fail 数组),会因为初始化开销过大而被其他算法替代。例如 CPython 中使用的就是 boyer-moore 算法和 horspool 算法 的混合版本。但是 KMP 算法的复杂度和代码量都比较小,刷题时很有帮助。
next 数组 Link to heading
next 数组是用来加速字符串匹配速度的,当某次字符串匹配失败时,如果从第 0 个字符重新开始匹配速度太慢,可以考虑利用一些额外信息。
next[i] 考虑为,对于模式字符串的子串 p[0] ~ p[i],子串前后缀相同的最长长度 k(k 不能为 i+1,因为此时前后缀都是子串本身了)。
例如:“abc” 的 next 数组就是 [0,0,0],因为不管对于哪个子串,前后缀相同的最长长度都是 0。“abcabc” 的 next 数组是 [0,0,0,1,2,3],子串 “abcab”,有前缀 “ab” 等于后缀 “ab”。
利用 next 数组 Link to heading
依旧利用 BF 方法进行匹配。但当出现字符不相等的情况时,将模式字符串的指针回溯到 next[pos-1] 的位置。因为我们是在匹配 p[pos] 出现不相等的,但是 p[0] ~ p[pos-1] 都是相等的。如果 next[pos-1] > 0,那么说明 p[0] ~ p[pos-1] 中有 next[pos-1] 个字符首尾相同。那我们就直接从 p[next[pos-1]] 再开始匹配就好啦(首尾相同的后一个)。
代码 Link to heading
1def kmp(s, p):
2 nxt = [0]
3 x = 1 # 字符串尾指针
4 now = 0 # nxt 匹配位置
5
6 while x < len(p):
7 if p[x] == p[now]:
8 x += 1
9 now += 1
10 nxt.append(now)
11 elif now:
12 now = nxt[now-1]
13 else:
14 x += 1
15 nxt.append(0)
16
17 tar = 0
18 pos = 0
19 while tar < len(s):
20 if s[tar] == p[pos]:
21 tar += 1
22 pos += 1
23 if pos == len(p):
24 yield (tar - pos)
25 pos = nxt[pos-1]
26 elif pos:
27 pos = nxt[pos-1]
28 else:
29 tar += 1
1function *KMP(s: string, p: string) {
2 const nxt = [0]
3
4 let x = 1, now = 0
5 while (x < len(p)) {
6 if (p[x] === p[now]) {
7 x++, now++
8 nxt.append(now)
9 } else if (now) {
10 now = nxt[now-1]
11 } else {
12 x++
13 nxt.append(0)
14 }
15 }
16
17 let pos = 0, tar = 0
18 while (tar < len(s)) {
19 if (s[tar] === p[pos]) {
20 tar++, pos++
21 if (pos == len(p)) {
22 yield tar - pos
23 pos = nxt[pos-1]
24 }
25 } else if (pos) {
26 pos = nxt[pos-1]
27 } else {
28 tar += 1
29 }
30 }
31}