简介
KMP 算法是一种字符串匹配算法,字符串匹配应用比较广泛,比如网页文本搜索,在文本种找到某个模式的所有出现位置,在 DNA 序列种搜寻特定的序列等。该算法是由 Knuth、Morris 和 Pratt 三人设计的,可以在线性时间字符串匹配的算法。其他匹配算法还有暴力算法、Rabin-Karp 算法、有限自动机算法(用于正则表达式、词法分析等),下面我们主要介绍 KMP 算法
复杂度
设定主串长度为 n、模式串长度为 m
匹配时间复杂度 O(n)
辅助函数计算时间复杂度 O(m)
辅助函数空间复杂度 O(m)
辅助函数通过在 next 数组中记录模式串的前后缀信息,在字符串匹配过程种避免主串回溯,从而达到 O(n) 的匹配时间复杂度
算法详解
1. next 数组
next 数组是对于模式串 P 而言的
设定 next*[i] = k,它表示模式串 P 从下标 0 到下标 i 的子串中,前缀与后缀相同的最大字符数 k 其中 k <= i - 1。
next 数组是相对于 next* 数组整体右移一位,首位补 -1,主要是为了方便使用,利用 next* 数组也可以进行 KMP 算法
示例:
模式串P | a | b | a | b | a | b | c | b |
---|---|---|---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
next* | 0 | 0 | 1 | 2 | 3 | 4 | 0 | NA |
next | -1 | 0 | 0 | 1 | 2 | 3 | 4 | 0 |
next*[0] = 0,前后缀为空字符串
next*[1] = 0,前后缀均不相同
next*[2] = 1,存在最长相同前后缀 “a”
next*[3] = 2,存在最长相同前后缀 “ab”
next*[4] = 3,存在最长相同前后缀 “aba”
next*[5] = 4,存在最长相同前后缀 “abab”
next*[6] = 0,前后缀均不相同
next* 数组求解过程
2. KMP 算法
匹配过程
上面的匹配过程种,主串并不会回溯,遇到匹配失败,则会根据 next 数据调整模式串匹配位置,主串种匹配失败的位置可能会进行多次匹配,但是整体复杂度并不会超过 O(n),算法导论中有对 KMP 算法时间复杂度的摊还分析,有兴趣的可以看一下
实现代码
1. 求解 next 数组代码
void getNext(char * p, int * next) {
next[0] = -1;
int i = 0, j = -1;
while (i < (int)strlen(p)) {
if (j == -1 || p[i] == p[j]) {
++i;
++j;
next[i] = j;
} else {
j = next[j];
}
}
}
2. KMP 匹配
int kmp(char * t, char * p) {
int i = 0;
int j = 0;
while (i < (int)strlen(t) && j < (int)strlen(p)) {
if (j == -1 || t[i] == p[j]) {
i++;
j++;
} else {
j = next[j];
}
}
if (j == strlen(p))
return i - j;
else
return -1;
}