算法与数据结构——字符串匹配——KMP
1. 普通的字符串匹配
有两个字符串如下
0 1 2 3 4 5 6 7 8 9
字符串t
d a b a c a b a b a c
字符串p
a b a b
通常可以这样写
char *
func(char *t, char *p)
{
for (i = 0; i < strlen(t); i++) {
for (j = 0; j < strlen(p); j++) {
if (t[i + j] != p[j])
break;
}
if (j == strlen(p))
return t + i - strlen(p);
}
return NULL;
}
这个算法的时间复杂度是 O((m-n+1)*n)
这个算法的问题在于 这一步
for (i = 0; i < strlen(t); i++) {
for (j = 0; j < strlen(p); j++) { // 问题
if (t[i + j] != p[j])
break;
}
}
当发生不匹配时,每次都从 0 开始重新匹配,
之所以如此,是因为 我们无法知道 t[i] 是 p[0] - p[strlen(p)]的哪个位置。
所以只能 把每个位置都尝试一次。
2. KMP算法
KMP 针对如上问题改进。
其关键是能确定 t[i] 在 p[0] - p[strlen(p)] 的位置,
如此就有
char *
kmp(char *t, char *p)
{
for (i = 0, j = 0; i < strlen(t) && j < strlen(p) && j != -1; i++) {
if (t[i] != p[j])
j = next[j];
else
j++;
}
if (j == strlen(p))
return t + i - strlen(p);
return NULL;
}
KMP 的时间复杂度为 O(m+n)
其关键是,当发生不匹配时,预先知道 t[i] 相对于 p 串的位置,为 next[j]
if (t[i] != p[j])
j = next[j];
2.1 next数组
由于已知 p[n] - p[m] 和 t[i] - t[j] 已经匹配,
在 p[m+1] 处发生不匹配,
则 希望知道 p[m+1] 相对于 t[k]。
实际上比较的是,下一个可能匹配的子串 的前缀 和 已匹配 子串 后缀的。
而只有当一个串的前缀,后缀 相同时,k>0,否则k=0,
例如,串 abab 可以这么求
a : 前缀没有,后缀没有,相同部分 0
ab : 前缀a, 后缀b, 相同部分长度 0
aba : 前缀a, 后缀a, 相同部分长度 1
abab : 前缀ab,后缀ab,相同部分长度 2
next数组是除掉当前字符的最大 相同前后缀长度,所以对上面数值右移一位,第一个元素设为-1.
所以能构建next数组
void getnext(char *p, char *next)
{
next[0] = -1;
int i = 0, j = -1;
while (i < strlen(p)) {
if (j == -1 || p[i] == p[j]) {
i++;
j++;
next[i] = j;
}
else
j = next[j];
}
}
由于模式串是提前知道的,所以对模式串求 next数组,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?