字符串模式匹配查找
字符串模式匹配(String Matching)是指在主串中查找与模式串相同的子串位置。常见算法有朴素算法、KMP 算法等。
字符串模式匹配演示
此处可扩展为KMP等算法的匹配过程动画。
- 主串与模式串对齐
- next数组跳转演示
- 匹配/失配过程
朴素算法(Brute Force)
- 逐一对齐主串和模式串,依次比较字符
- 时间复杂度 ,为主串长度,为模式串长度
KMP 算法
- 利用部分匹配表(next 数组)避免重复比较
- 时间复杂度
- 文本编辑器查找、替换
- DNA 序列分析
- 信息检索、数据挖掘
朴素算法伪代码:
for i = 0 to m-n:
for j = 0 to n-1:
if S[i+j] != P[j]:
break
if j == n:
return i // 匹配成功
return -1
KMP 算法伪代码(简化):
构建next数组
i = 0, j = 0
while i < m:
if S[i] == P[j]:
i++, j++
if j == n: return i-n // 匹配成功
else if j > 0:
j = next[j-1]
else:
i++
return -1
next 数组是 KMP 的核心:当模式串 在 处失配时, 应该回退到 next[j] 的位置。next[j] 等于 P[0..j-1] 的最长相等前后缀长度。
求 next 数组的步骤
next[0] = -1(第一个字符失配,主串指针后移)- 递推求 next[j]:令 k = next[j-1],若 P[j-1] == P[k],则 next[j] = k+1;否则 k = next[k] 继续回退
示例:模式串 “abaabc”
| j | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| P[j] | a | b | a | a | b | c |
| next[j] | -1 | 0 | 0 | 1 | 1 | 2 |
推导过程:
next[0] = -1j=1:子串a无前后缀,next[1] = 0j=2:子串ab最长相等前后缀为 0,next[2] = 0j=3:子串aba最长相等前后缀为a,长度 1,next[3] = 1j=4:子串abaa最长相等前后缀为a,长度 1,next[4] = 1j=5:子串abaab最长相等前后缀为ab,长度 2,next[5] = 2
作用:在 P[j] 失配时,模式串指针回退到 next[j] 继续比较,而主串指针 i 不回溯,从而保证整体 。
KMP 算法代码实现
// 求 next 数组
void GetNext(char P[], int next[], int n) {
next[0] = -1;
int i = 0, k = -1;
while (i < n - 1) {
if (k == -1 || P[i] == P[k]) {
i++;
k++;
next[i] = k;
} else {
k = next[k]; // 回退
}
}
}
// KMP 匹配,S 为主串(长 m),P 为模式串(长 n)
int KMP(char S[], char P[], int next[]) {
int i = 0, j = 0;
while (i < (int)strlen(S) && j < (int)strlen(P)) {
if (j == -1 || S[i] == P[j]) {
i++;
j++;
} else {
j = next[j]; // 主串不回溯,模式串回退
}
}
if (j == (int)strlen(P))
return i - j; // 匹配成功,返回起始位置
return -1; // 匹配失败
}
- 朴素算法:
- KMP 算法:
习题 1
简述 KMP 算法的基本思想及其优点。
解题思路:
本题考查 KMP 算法的基本思想与优势。核心思想是预先为模式串构造 next 数组记录其前缀信息,匹配失败时据此直接跳过已经确认匹配过的部分,而不是从头逐个重新比较。
详细步骤:
- 利用 next 数组避免重复比较
- 提高匹配效率,时间复杂度
- 补充:朴素算法在失配后主串指针要回退、模式串从头重比,代价为 ;KMP 通过 next 数组把模式串指针跳到已匹配前缀的最长可延伸位置,使已比较过的字符不必重复比较,从而把复杂度降为 。
最终答案:
KMP 用 next 数组跳过已知匹配前缀,效率高。
习题 2
朴素算法和 KMP 算法的时间复杂度分别是多少?
解题思路:
本题考查两种字符串匹配算法的复杂度对比。关键在于失配之后是否需要把主串指针回退并从头重比,这一步决定了总代价是乘积级还是线性级。
详细步骤:
- 朴素算法
- KMP 算法
- 补充:设主串长为 、模式串长为 。朴素算法最坏情况下主串的每个位置都要与模式串逐字符比较,代价为 ;KMP 借 next 数组使主串指针只前进不回退,总比较次数为线性量级,故为 。
最终答案:
朴素,KMP。
习题 3
KMP 算法如何避免主串指针回溯?
解题思路:
本题要求说明 KMP 避免主串回溯的机制。答案是把“回退”从主串转移到模式串:主串指针始终只向前移动,需要调整的只是模式串指针。
详细步骤:
- 匹配失败时,主串指针不回溯
- 模式串指针根据 next 数组跳转
- 补充:next 数组记录了模式串各位置的最长相等前后缀长度,失配时据此把模式串指针跳到该长度对应的位置,使主串当前位置可以与新的模式串字符直接比较;由于跳过的正是此前已确认匹配的前缀,这些字符无须重新比较。
最终答案:
KMP 用 next 数组避免主串回溯。
