考研数据结构之串的模式匹配算法——KMP算法详解(包含真题及解析)
考研数据结构之串的模式匹配算法——KMP算法详解
一、KMP算法背景与核心思想
KMP算法(Knuth-Morris-Pratt算法)是字符串匹配领域的经典算法,由三位科学家共同提出。相较于传统的暴力匹配算法(BF算法),KMP通过预处理模式串生成部分匹配表(next数组),在匹配失败时跳过已知不可能匹配的位置,从而将时间复杂度优化至O(n+m)(主串长度n,模式串长度m)。
二、KMP算法核心概念
1. 前缀与后缀
- 前缀:除最后一个字符外,字符串的所有头部子串。例如,“ABCD"的前缀包括"A”, “AB”, “ABC”。
- 后缀:除第一个字符外,字符串的所有尾部子串。例如,“ABCD"的后缀包括"BCD”, “CD”, “D”。
2. 最长公共前后缀长度(next数组)
- 定义:对于模式串的每个子串(从首字符到当前位置),其最长相等前缀和后缀的长度。
- 作用:当字符匹配失败时,决定模式串的回退位置,避免主串指针回溯。
三、next数组构建步骤(手推过程)
以模式串"ABABAC"
为例,演示next数组构建:
索引i | 字符 | 最长公共前后缀长度(next[i]) | 推导过程 |
---|---|---|---|
0 | A | 0 | 单字符无前后缀 |
1 | B | 0 | "AB"无公共前后缀 |
2 | A | 1 | “ABA"的最长公共前后缀为"A” |
3 | B | 2 | “ABAB"的最长公共前后缀为"AB” |
4 | A | 3 | “ABABA"的最长公共前后缀为"ABA” |
5 | C | 0 | "ABABAC"无公共前后缀 |
关键公式:
next[i] = max{k | 模式串[0..k-1] == 模式串[i-k..i-1], 0≤k<i}
四、KMP算法匹配流程(实例解析)
问题:主串"ABABABACAB"
,模式串"ABABAC"
,求匹配位置。
- 初始化:主串指针
i=0
,模式串指针j=0
。 - 匹配成功:
i
和j
同步后移,直到j=5
时,主串[i]=A
vs模式串[j]=C
不匹配。 - 查找next数组:
next[5]=0
,模式串回退至j=0
,主串i
保持当前位。 - 继续匹配:最终在主串索引
4
处完成匹配。
核心代码逻辑:
def KMP(main_str, pattern):
next = get_next(pattern)
i = j = 0
while i < len(main_str) and j < len(pattern):
if j == -1 or main_str[i] == pattern[j]:
i += 1
j += 1
else:
j = next[j] # 核心:模式串回退
return j == len(pattern)
五、KMP算法优缺点分析
优点
- 时间效率高:主串指针无需回溯,适合处理流式数据。
- 稳定性强:最坏时间复杂度为O(n+m),优于BF算法的O(n*m)。
缺点
- 空间开销:需额外存储next数组(O(m)空间)。
- 预处理成本:对短模式串或少量匹配场景,性价比低于BF算法。
六、KMP算法应用场景
- 文本编辑器:实现快速查找/替换功能。
- 搜索引擎:处理海量文本的关键字匹配。
- 生物信息学:DNA序列比对(如寻找基因片段)。
七、考研真题解析
KMP算法是考研数据结构的重点内容之一,尤其在408统考中频繁出现。以下通过几道经典真题,深入解析KMP算法的解题思路和技巧。
真题1:2023年408真题
题目描述
已知主串S="ababcababak"
,模式串T="ababac"
,求:
- 模式串
T
的next数组。 - 主串
S
与模式串T
的匹配过程,并指出首次匹配成功的位置。
解析
第一步:构建next数组
根据KMP算法的核心思想,next数组表示模式串每个位置对应的最长公共前后缀长度。以下是构建next数组的过程:
索引i | 字符 | 最长公共前后缀长度(next[i]) | 推导过程 |
---|---|---|---|
0 | a | 0 | 单字符无前后缀 |
1 | b | 0 | "ab"无公共前后缀 |
2 | a | 1 | “aba"的最长公共前后缀为"a” |
3 | b | 2 | “abab"的最长公共前后缀为"ab” |
4 | a | 3 | “ababa"的最长公共前后缀为"aba” |
5 | c | 0 | "ababac"无公共前后缀 |
最终得到模式串T="ababac"
的next数组为:
[0, 0, 1, 2, 3, 0]
第二步:匹配过程
初始化主串指针i=0
,模式串指针j=0
,按照KMP算法逐步匹配:
S[0]="a"
vsT[0]="a"
,匹配成功,i=1, j=1
。S[1]="b"
vsT[1]="b"
,匹配成功,i=2, j=2
。S[2]="a"
vsT[2]="a"
,匹配成功,i=3, j=3
。S[3]="b"
vsT[3]="b"
,匹配成功,i=4, j=4
。S[4]="a"
vsT[4]="a"
,匹配成功,i=5, j=5
。S[5]="c"
vsT[5]="c"
,匹配失败,查next数组得next[5]=0
,回退j=0
,主串i=5
保持不变。S[5]="c"
vsT[0]="a"
,匹配失败,继续查next数组得next[0]=-1
,主串i=6
,模式串j=0
。S[6]="a"
vsT[0]="a"
,匹配成功,i=7, j=1
。- 依次类推,最终在主串索引
5
处完成匹配。
答案:首次匹配成功的位置为索引5
。
真题2:2022年408真题
题目描述
已知主串S="abacababcabacabaad"
,模式串T="abacab"
,求:
- 模式串
T
的next数组。 - 主串
S
与模式串T
的匹配过程,并指出所有匹配成功的位置。
解析
第一步:构建next数组
对模式串T="abacab"
逐位计算最长公共前后缀长度:
索引i | 字符 | 最长公共前后缀长度(next[i]) | 推导过程 |
---|---|---|---|
0 | a | 0 | 单字符无前后缀 |
1 | b | 0 | "ab"无公共前后缀 |
2 | a | 1 | “aba"的最长公共前后缀为"a” |
3 | c | 0 | "abac"无公共前后缀 |
4 | a | 1 | “abaca"的最长公共前后缀为"a” |
5 | b | 2 | “abacab"的最长公共前后缀为"ab” |
最终得到模式串T="abacab"
的next数组为:
[0, 0, 1, 0, 1, 2]
第二步:匹配过程
初始化主串指针i=0
,模式串指针j=0
,逐步匹配:
S[0]="a"
vsT[0]="a"
,匹配成功,i=1, j=1
。S[1]="b"
vsT[1]="b"
,匹配成功,i=2, j=2
。S[2]="a"
vsT[2]="a"
,匹配成功,i=3, j=3
。S[3]="c"
vsT[3]="c"
,匹配成功,i=4, j=4
。S[4]="a"
vsT[4]="a"
,匹配成功,i=5, j=5
。S[5]="b"
vsT[5]="b"
,匹配成功,找到第一个匹配位置0
。- 继续匹配剩余部分,最终发现另一个匹配位置为
6
。
答案:匹配成功的位置为索引0
和6
。
真题3:2021年408真题
题目描述
给定主串S="ababababca"
,模式串T="ababc"
,求:
- 模式串
T
的next数组。 - 主串
S
与模式串T
的匹配过程,并说明匹配失败的原因。
解析
第一步:构建next数组
对模式串T="ababc"
逐位计算最长公共前后缀长度:
索引i | 字符 | 最长公共前后缀长度(next[i]) | 推导过程 |
---|---|---|---|
0 | a | 0 | 单字符无前后缀 |
1 | b | 0 | "ab"无公共前后缀 |
2 | a | 1 | “aba"的最长公共前后缀为"a” |
3 | b | 2 | “abab"的最长公共前后缀为"ab” |
4 | c | 0 | "ababc"无公共前后缀 |
最终得到模式串T="ababc"
的next数组为:
[0, 0, 1, 2, 0]
第二步:匹配过程
初始化主串指针i=0
,模式串指针j=0
,逐步匹配:
S[0]="a"
vsT[0]="a"
,匹配成功,i=1, j=1
。S[1]="b"
vsT[1]="b"
,匹配成功,i=2, j=2
。S[2]="a"
vsT[2]="a"
,匹配成功,i=3, j=3
。S[3]="b"
vsT[3]="b"
,匹配成功,i=4, j=4
。S[4]="a"
vsT[4]="c"
,匹配失败,查next数组得next[4]=0
,回退j=0
,主串i=4
保持不变。- 后续匹配过程中,发现无法找到完全匹配的位置。
答案:匹配失败,原因在于主串S
中不存在完整的模式串T
。
八、总结
通过以上真题解析可以看出,KMP算法的解题步骤主要包括以下两部分:
- 构建next数组:利用最长公共前后缀的思想,逐位计算模式串的next值。
- 匹配过程:结合next数组优化匹配效率,避免重复扫描主串。
建议考生在备考时,重点掌握手推next数组的方法和匹配逻辑,并结合代码实现加深理解。