KMP算法讲解
KMP介绍
来源:百度百科
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。
KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
常规问题
给定一个字符串作为主串,再给定一个字符串作为模式串。
模式串可能在主串中重复出现,求每次出现的下标位置(主串下标从1开始)。
如果没有出现过,答案即为-1。
暴力解法
类似双指针、单调栈、单调队列,这些算法首先做出暴力解法,再进行优化。去掉一些不需要的情况判断(剪枝)。
int S[M], P[N] // S是主串,P是模式串(子串)
// 暴力循环
for(int i = 1; i <= m; ++i) {
for(int j = 1; j <= n; ++j) {
// 如果存在不匹配的情况,则当前i不符合
if(s[i + j - 1] != s[j])
break;
}
}
易知暴力解法时间复杂度较高,且有很多情况重复判断。
优化解法
匹配思路
KMP匹配的思路
S[M]是主串,P[N]是模式串。
- 图中可以看到S[M]紫色区域是已经匹配过的区域。
- 当前正在匹配的区域是黄色开始的,黄色区域为主串和模式串相同的区域。
- 在红色区域,此时取单个字符,在遍历到这个字符时主串与模式串不等。
- 绿色区域是没有开始匹配的区域。

此时发现不匹配的元素,移动模式串进行新的位置匹配。
暴力解法中,此时模式串会向后移动一位,从主串的下一个元素开始匹配,时间复杂度高。
此处进行优化,如果存在一个元素,模式串可以移动到这个元素处,进行下一轮的匹配,就可以节省很多情况。
具体见下图:
P[N]向后移动数个元素之后,
- 此时蓝色区域的元素相等,则新一轮匹配可以从此处开始。即P[N]跳过了主串此时黄色区域的匹配情况,节省了时间。
- 灰色区域是模式串未匹配区域。

接下来判断蓝色区域之后的元素是否匹配,遇到不匹配的元素则重复进行上述步骤,直到遍历完主串或者模式串匹配成功。
我们可以看到,模式串向后移动到的位置,即可以继续进行匹配的位置。这个距离,即next数组的元素。
next数组构建
对模式串每个元素构建next数组
next数组构建方法是将模式串自己与自己匹配,具体过程和KMP匹配过程类似。
声明几个概念:
- "非平凡后缀":是指以当前字符串首字符开始,除去当前字符串中最后一个元素的所有子串情况;
- "非平凡前缀":同上,指以当前字符串尾字符终止,除去当前字符串中第一个元素的所有子串情况;
- "部分匹配值":指同一字符串中,非平凡后缀与非平凡前缀的最长公共子串。
next数组中的元素是"部分匹配值"的长度。构建过程举例:
模式串P[7] = "abcabca",下标从1开始
| a | b | c | a | b | c | a |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- next[1] = 0,易知为0;
- {a}——{b},next[2] = 0;
- {a},{ab}——{c},{bc},next[3] = 0;
- {a},{ab},{abc}——{a},{ca},{bca},next[4] = 1;
- {a},{ab},{abc},{abca}——{b},{ab},{cab},{bcab},next[5] = 2;
- {a},{ab},{abc},{abca},{abcab}——{c},{bc},{abc},{cabc},{bcabc},next[6] = 3;
- {a},{ab},{abc},{abca},{abcab},{abcabc}——{a},{ca},{bca},{abca},{cabca},{bcabca},next[7] = 4。
则构建next数组为
| 0 | 0 | 0 | 1 | 2 | 3 | 4 |
|---|
构建next数组是KMP算法的核心!
练习题目及代码
题目快照(题目来源acwing网站)

完整代码
#include <iostream>
using namespace std;
const int N = 10010, M = 100010;
int n, m;
char p[N], s[M];
int next[N]; // 初始化为0
int main() {
cin >> n >> p + 1 >> m >> s + 1; // p,s均为字符数组的首地址指针
// 构建next数组
for(int i = 2, j = 0; i <= n; ++i) { // next[1] = 0,故i从2开始
while(j && p[i] != p[j + 1])
j = next[j];
if(p[i] == p[j + 1])
++j;
next[i] = j;
}
// KMP匹配过程
for(int i = 1, j = 0; i <= m; ++i) {
while(j && s[i] != p[j + 1])
j = next[j];
if(s[i] == p[j + 1])
++j;
if(j == n) { // 匹配成功
// 本题要求返回每次成功匹配的起始下标位置
cout << i - m << endl; //(注意此时字符数组下标从1开始)
j = next[j]; // 细节问题
}
}
return 0;
}

KMP算法的讲解,包含练习题目及代码。
浙公网安备 33010602011771号