考点频率 :★★★★★(数据结构必考,选择题常考next数组的计算,下午题偶尔出现)
难度 :⭐⭐⭐⭐⭐(数据结构中最难理解的内容之一)
建议:重点掌握next数组的推导方法,理解KMP如何避免主串指针回溯,会手动计算next数组值
1️⃣ 什么是串的模式匹配?
模式匹配 是指:在主串(目标串) 中查找模式串(子串) 的位置。
- 主串 :被查找的字符串,如
"ABABCABAB"(可以理解为"长文本") - 模式串 :要查找的字符串,如
"ABAB"(可以理解为"关键词")
生活类比 :你在Word里按
Ctrl+F搜索一个词,Word会在整篇文档(主串)里找这个词(模式串),并告诉你它在第几页第几行------这就是模式匹配。
2️⃣ 朴素匹配(BF算法)的痛点
朴素匹配(Brute-Force,BF) 是最直接的思路:从主串的每个位置开始,逐个字符与模式串比较,匹配失败就回溯到下一个位置重新开始。
主串:A B A B C A B A B
模式:A B A B
第1轮:A B A B ✅ 匹配成功!
(这就是为什么它叫"暴力"------它从主串的每个位置都试一遍,直到找到匹配或试完所有位置)
BF的时间复杂度 :O(n×m)O(n \times m)O(n×m),其中 nnn 是主串长度,mmm 是模式串长度。最坏情况下(如主串为 "AAAA...AB",模式串为 "AAAA...AC"),几乎每个位置都要比较 mmm 次。
BF的痛点 :每次匹配失败后,主串的指针要回溯到开始位置的下一个字符,之前比较过的信息全部丢弃,导致大量重复比较。
3️⃣ KMP算法的核心思想
KMP算法 (Knuth-Morris-Pratt)的核心是:匹配失败时,主串指针不回溯,只移动模式串指针。
- 主串指针(i):只前进,不后退
- 模式串指针(j) :根据
next数组回退到某个位置,继续匹配
生活类比:你看一本书找一句话。BF算法是:每次发现不对,就把书合上,翻回开头下一页重新看。KMP是:你手里夹着一张书签(next数组),发现不对时,书签告诉你"别翻回去,从书签标记的那一页接着看就行"------你只往前翻,永远不往回翻。
KMP的工作流程
- 主串指针
i从 0 开始,模式串指针j从 0 开始 - 如果
主串[i] == 模式串[j],i++,j++ - 如果
j已经到达模式串末尾 → 匹配成功,返回i - j - 如果
主串[i] != 模式串[j]:- 如果
j == 0:i++(第一个字符都不匹配,主串指针前移一位) - 否则:
j = next[j](模式串指针回退到next[j],主串指针i不动)
- 如果
4️⃣ next数组的含义(核心)
next[j] 表示:当模式串中第 j 个字符与主串不匹配时,模式串指针应该回退到的位置。
next 数组的值仅取决于模式串本身 ,与主串无关。这意味着,对于任意给定的模式串,我们只需要事先算好一个固定的 next 数组,就可以在任何主串中使用它来加速匹配------一次计算,到处使用。
核心理解 :
next[j]是模式串前j个字符中,最长相等前后缀的长度。它是一个"真前缀"与"真后缀"的匹配度,表示当匹配失败时,模式串可以"复用"多少已经比较过的信息。
具体含义 :当模式串的 j 位置匹配失败时(即 主串[i] != 模式串[j]),模式串的 j 指针回退到 next[j],继续与主串的当前位置 i 比较 (主串指针 i 不动)。
5️⃣ 如何手工计算 next 数组
5.1 前缀、后缀、最长相等前后缀
- 前缀:除最后一个字符外,从第一个字符开始的连续子串
- 后缀:除第一个字符外,到最后一个字符结束的连续子串
- 最长相等前后缀:前缀集合和后缀集合的交集中,长度最长的那个
示例 :模式串
"ABAB",计算前3个字符"ABA"的最长相等前后缀。
- 前缀:
"A","AB"- 后缀:
"A","BA"- 交集:
"A"- 最长相等前后缀长度 = 1
5.2 手算 next 数组(下标从 0 开始)
设模式串为 P,长度为 m。next[0] = -1(第一个字符不匹配时,主串指针前移)。
对于 j >= 1,next[j] = 模式串 P[0..j-1] 中最长相等前后缀的长度。
| 模式串 | j | 前 j 个字符 | 最长相等前后缀长度 | nextj |
|---|---|---|---|---|
"ABAB" |
0 | - | - | -1 |
| 1 | "A" |
0 | 0 | |
| 2 | "AB" |
0 | 0 | |
| 3 | "ABA" |
1("A") |
1 |
所以 next = [-1, 0, 0, 1]
5.3 另一种常见定义(下标从 1 开始,考试可能遇到)
有些教材中,next[1] = 0,next[j] = 模式串 P[1..j-1] 中最长相等前后缀长度 + 1。
这种定义下,next 数组的值整体比下标从0开始的定义大1。考试中如果遇到,看清题目给出的定义方式,按照题目给定的定义来计算。
6️⃣ KMP算法的匹配过程示例
主串 :"ABABCABAB"
模式串 :"ABAB"
next :[-1, 0, 0, 1]
第1轮:i=0, j=0
主串:A B A B C A B A B
模式:A B A B
↑
匹配:A == A ✅ → i=1, j=1
第2轮:i=1, j=1
主串:A B A B C A B A B
模式:A B A B
↑
匹配:B == B ✅ → i=2, j=2
第3轮:i=2, j=2
主串:A B A B C A B A B
模式:A B A B
↑
匹配:A == A ✅ → i=3, j=3
第4轮:i=3, j=3
主串:A B A B C A B A B
模式:A B A B
↑
匹配:B == B ✅ → i=4, j=4
第5轮:j == 4(模式串长度),匹配成功!
匹配位置 = i - j = 4 - 4 = 0(主串中从下标0开始的子串匹配)
实际上KMP的优势主要体现在匹配失败时。上面的例子匹配过程非常顺利,没有触发回退。为了更好地展示KMP的"主串指针不回溯"特点,下面换一个更典型的场景:
场景 :主串 "ABABABC",模式串 "ABABC",匹配到第5个字符时失败。
第1-4轮:A B A B 都匹配成功(i=4, j=4)
第5轮:主串[i] = A,模式串[j] = C → 匹配失败!
此时主串指针 i=4(指向'A'),模式串指针 j=4(指向'C')
BF做法:i 回溯到 1,重新从模式串第0位开始比较 → 已比较的信息全丢失
KMP做法:查 next[4] = 1,j = 1,i 不动(仍为4)
模式串指针从1开始继续与主串的 i=4 位置比较
主串指针 i 没有回溯,这就是KMP效率高的根本原因。
7️⃣ KMP vs BF 对比
| 对比项 | BF(朴素匹配) | KMP |
|---|---|---|
| 主串指针 | 匹配失败时回溯 | 永不回溯 |
| 模式串指针 | 每次都回到0 | 根据next数组回退 |
| 时间复杂度 | O(n×m)O(n \times m)O(n×m) | O(n+m)O(n + m)O(n+m) |
| 额外空间 | 无 | O(m)O(m)O(m)(next数组) |
| 预处理 | 无 | 需要计算next数组 |
8️⃣ 经典例题
例题1 :模式串 "ABCDABD" 的 next 数组(下标从0开始)为( )。
解析:
next[0] = -1j=1:前1个字符"A"→ 最长相等前后缀长度 0 →next[1] = 0j=2:前2个字符"AB"→ 最长相等前后缀长度 0 →next[2] = 0j=3:前3个字符"ABC"→ 最长相等前后缀长度 0 →next[3] = 0j=4:前4个字符"ABCD"→ 最长相等前后缀长度 0 →next[4] = 0j=5:前5个字符"ABCDA"→ 前缀"A"= 后缀"A"→ 长度 1 →next[5] = 1j=6:前6个字符"ABCDAB"→ 前缀"AB"= 后缀"AB"→ 长度 2 →next[6] = 2
答案 :[-1, 0, 0, 0, 0, 1, 2]
例题2 :已知模式串 P = "ABABC",next 数组为 [-1, 0, 0, 1, 2]。当匹配到 j=4 时失配,模式串指针应回退到( )。
A. 0
B. 1
C. 2
D. 3
解析 :next[4] = 2,回退到 2。选 C。
例题3(判断):KMP算法中,当匹配失败时,主串指针需要回溯到之前的位置重新比较。( )
解析 :错误。KMP的核心优势就是主串指针永不回溯,只移动模式串指针。
9️⃣ 记忆口诀
KMP核心不回溯,主串指针只前进。
next数组看模式,最长相等前后缀。
next[0] = -1,next[j]算前 j 个字符的最长相等前后缀长度。匹配失败看 next,模式指针跳过去。
🔟 小测验(评论区对答案)
模式串
"AAAB"的 next 数组(下标从0开始)为( )。A.
[-1, 0, 0, 0]B.
[-1, 0, 1, 1]C.
[-1, 0, 1, 2]D.
[-1, 0, 0, 1]
🔔 本专栏日更,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容
#软考中级 #软件设计师 #KMP算法 #模式匹配 #数据结构 #软考备考