**朴素字符串匹配算法(Brute-Force,简称BF算法)**的核心实现,其基本思想是暴力枚举主串中每一个可能的起始位置,逐字符与模式串进行匹配。
1. 算法核心思想
从主串的第一个字符开始,将模式串逐位与主串对齐比较:
- 如果当前字符匹配,则继续比较后续字符;
- 一旦出现不匹配,则主串指针回退到本次匹配的起始位置的下一个位置,模式串指针重置为0,重新开始匹配;
- 若模式串全部字符都成功匹配,则返回在主串中的起始下标;
- 若遍历完主串仍未找到完全匹配的位置,则返回 -1。
2. 代码实现(C语言)
#include <stdio.h>
#include <string.h>
// 朴素字符串匹配算法
int bfSearch(char* s, char* t) {
int i = 0; // 主串指针
int j = 0; // 模式串指针
int slen = strlen(s);
int tlen = strlen(t);
while (i < slen && j < tlen) {
if (s[i] == t[j]) {
i++;
j++;
} else {
i = i - j + 1; // 主串回退
j = 0; // 模式串重置
}
}
// 匹配成功时,j等于tlen
if (j == tlen) {
return i - tlen; // 返回起始位置
} else {
return -1; // 匹配失败
}
}
// 示例测试
int main() {
char s[] = "ABABCABABA";
char t[] = "ABABA";
int index = bfSearch(s, t);
if (index != -1)
printf("匹配成功,起始下标为:%d\n", index);
else
printf("匹配失败\n");
return 0;
}
3. 时间复杂度分析
- 最好情况:每次匹配第一个字符就失败,或者很快失败,平均每个位置只需常数次比较。例如主串和模式串首字符差异大,时间复杂度接近
O(n + m)。 - 最坏情况:主串形如
"AAAAAAAAB",模式串为"AAAB",每次都要比较到末尾才失败,每趟比较m次,共n - m + 1趟,总时间复杂度为O((n - m + 1) * m),即O(n × m)。
✅ 优点:逻辑清晰、易于理解与实现。
❌ 缺点:效率低,尤其在重复字符多的情况下性能差。
🔧 改进方向:使用 KMP、Boyer-Moore、Sunday 等更高效的字符串匹配算法优化最坏情况下的表现。
KMP(Knuth-Morris-Pratt)算法通过预处理模式串构建“部分匹配表”(即next数组),利用模式串自身的重复信息,在发生不匹配时不回退主串指针,而是将模式串尽可能向右滑动到一个合理位置,继续比较,从而避免重复匹配,提升效率。
核心思想:利用模式串的“最长公共前后缀”进行跳跃
当主串 s[i] 与模式串 t[j] 不匹配时:
- 朴素算法会将主串指针
i回退到起始位置的下一个位置,重新从j=0开始匹配; - KMP算法则认为:如果模式串前缀中存在重复结构(如
"ABAB"中"AB"是前后缀),就可以利用这个结构,把模式串“滑动”到下一个可能匹配的位置,而主串指针i不需要回退,只需调整模式串指针j到next[j-1]的位置继续比较。
1. next数组定义
next[j] 表示:在模式串中,以 j 结尾的子串的最长相等真前后缀长度(真前后缀指不包括整个字符串本身)。
换句话说,next[j] 告诉我们:当 t[j] 匹配失败时,j 应该跳转到哪个位置继续匹配。
示例:
模式串:"ABABA"
j : 0 1 2 3 4
t[j] : A B A B A
next[j]:0 0 1 2 3
解释:
- j=0: 单字符无前后缀 → next[0]=0
- j=1: “AB” 无公共前后缀 → next[1]=0
- j=2: “ABA” 最长公共前后缀是 “A”(长度1)→ next[2]=1
- j=3: “ABAB” 公共前后缀 “AB”(长度2)→ next[3]=2
- j=4: “ABABA” 公共前后缀 “ABA”(长度3)→ next[4]=3
2. 匹配过程(不回退主串指针)
int kmpSearch(char* s, char* t) {
int i = 0, j = 0;
int slen = strlen(s), tlen = strlen(t);
int* next = buildNext(t); // 构建next数组
while (i < slen) {
if (j == -1 || s[i] == t[j]) {
i++;
j++;
} else {
j = next[j]; // 模式串指针跳转,主串i不回退
}
if (j == tlen) {
return i - tlen; // 找到匹配
}
}
return -1;
}
关键点:只有
j = next[j],而i一直向前走,没有回退!
3. 时间复杂度分析
- 构建next数组:O(m)
- 匹配过程:O(n)
- 总时间复杂度:O(n + m)
相比朴素算法最坏 O(n×m),KMP 在主串很长且模式串有重复结构时优势明显。
总结
KMP 算法之所以能避免主串指针回退,是因为它利用了模式串内部的自相似性(重复前缀),通过 next 数组指导模式串的滑动位置,使得每次失配后都能跳过不可能成功的匹配位置,主串只需遍历一次即可完成匹配。


915

被折叠的 条评论
为什么被折叠?



