kmp (未完成)
具体题目:P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
KMP算法是一种高效的字符串匹配算法,它的核心特点在于能够通过对主串进行单次遍历来实现匹配,从而达到O(n + m)的时间复杂度,其中n是主串的长度,而m是模式串的长度。
这种效率的关键在于KMP算法对模式串进行的预处理 —— 构建next数组。
NEXT数组
1.前后缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
2.next数组的核心概念
Next数组的核心概念在于记录模式串中每个子串的最长相同前后缀长度。具体地,对于长度为i+1的连续子串,其最长相同前后缀的长度等于 next[i]
的值。
以模式串 "aabaaf" 为例,其对应的 next 数组为 [0, 1, 0, 1, 2, 0]。在这个数组中,每个元素精确地映射了模式串中相应子串的最长相同前后缀长度。例如,next[4]
对应的是模式串中前 5 个字符(即 "aabaa")的最长相同前后缀,其长度为 2,对应的前后缀为 "aa"。
3.next数组的生成
// KMP算法的next数组生成函数
void kmpnext(char *t, int *next, int n) {
next[0] = 0; // next数组的第一个元素总是0,因为单个字符没有前后缀
// 使用双指针技术进行遍历,l代表前缀的结束位置,r代表后缀的结束位置
for (int l = 0, r = 1; r < n; r++) {
// 当前后缀字符不匹配时,回溯前缀指针l到next数组的上一个位置
while (t[l] != t[r] && l > 0) {
l = next[l - 1];
}
// 当前后缀字符匹配时,递增前缀指针l
if (t[l] == t[r] && l < r) {
l++;
}
// 更新next数组的当前位置
next[r] = l;
}
}
在此代码段中,我们实现了通过双指针方法生成KMP算法中的next数组.。
具体来说,我们将变量i作为后缀的起始指针,而j作为前缀的起始指针。在迭代过程中,我们比较前后缀的字符,若相等,则递增j的值,这表明我们找到了一个更长的相同前后缀。如果字符不匹配,我们则依据next数组已计算出的值来调整j的位置,这是一种回溯机制,允许算法在不匹配时跳过一些不必要的比较。
4.next数组的意义
前面我们折腾这么久,通过对模式串的预处理成功生成了next数组,那么,意义何在?
其实next数组是为了跳过模式串与主串匹配中的重复比较,如下图所示
可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,发现不匹配,此时就要从头匹配了。
但如果使用next数组,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。
kmp匹配
kmp匹配的代码实现
#include <stdio.h>
#include <string.h>
#define MAX 1000 // 假设字符串的最大长度为1000
// KMP算法的next数组生成函数的声明
void kmpnext(char *t, int *next, int lent);
int main() {
char s[MAX], t[MAX]; // s为主串,t为模式串
// 读入主串和模式串
scanf("%s", s);
scanf("%s", t);
// 计算主串和模式串的长度
int lens = strlen(s), lent = strlen(t);
// 定义next数组
int next[lent];
// 构建模式串的next数组
kmpnext(t, next, lent);
// KMP算法主体
int j = 0; // 初始化模式串的索引
for (int i = 0; i < lens; i++) { // 遍历主串
// 当字符不匹配时,利用next数组回溯模式串的索引
while (s[i] != t[j] && j > 0) j = next[j - 1];
// 当字符匹配时,递增模式串的索引
if (s[i] == t[j]) j++;
// 完全匹配模式串时,输出匹配的起始位置
if (j == lent) {
printf("%d\n", i - j + 2); // 输出匹配的起始位置
j = next[j - 1]; // 继续寻找下一个匹配
}
}
// 打印next数组
for (int i = 0; i < lent; i++) {
printf("%d ", next[i]);
}
return 0;
}
在此代码段中,我们同样通过双指针方法来进行主串与模式串的匹配。
具体来说,我们将变量i作为主串的起始指针,而j作为模式串的起始指针。如果i,j指针所指向的字符匹配,j指向模式串的下一个字符,i继续遍历主串;如果i,j指针所指向的字符不匹配,将j指针指向当前next数组中对应的位置,从上次已经匹配的内容开始匹配。最后如果j指向模式串的最后一个字符,说明匹配成功,记录匹配的起始位置,同时将j指针指向当前next数组中对应的位置,从上次已经匹配的内容开始匹配,直到遍历完主串为止。