KMP算法及其相关概念
KMP算法是一个字符串匹配算法,对暴力的那种一一比对的方法进行了优化,使时间复杂度大大降低。KMP算法的作用是在一个已知字符串中查找子串的位置,也叫做串的模式匹配。比如主串s=“goodgoogle”,子串t=“google”。现在我们要找到子串t 在主串s 中的位置。·
相关概念:
s[] : 模式串,较长的字符串
p[] : 模板串,简短的串
前缀:除了最后一个字符以外,该字符串的全部头部组合
后缀:除了第一个字符以外,该字符串的全部尾部组合
部分匹配表:一个字符串的前缀和后缀的最长公有元素的长度
ne[i] : 以i为结尾的部分匹配的值,ne数组即为部分匹配表。这里ne[]有两个概念,一是next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度。二是表示该处字符不匹配时应该回溯到的字符的下标。
idx : 数组的下标
前缀后缀,部分匹配表介绍
ne[] 也就是next[] ,是字符串最长想等前后缀的长度
举个例子:求字符串数组P = {abcab)的前缀
P | a | b | c | a | b |
---|---|---|---|---|---|
idx | 1 | 2 | 3 | 4 | 5 |
ne[] | 0 | 0 | 0 | 1 | 2 |
ne[1] = 0 -> 前缀 = 后缀 = 空集
ne[2] = 0 -> 前缀={a} 后缀 = {b} ,前缀后缀没有交集 因此ne[2] = 0
ne[3] :前缀 = { a , ab } , 后缀 = { c , bc} , ne[ 3 ] = 0;
ne[4] :前缀 = { a , ab , abc } ,后缀 = { a . ca , bca },next[ 4 ] = 1;
ne[5] :前缀 = { a , ab , abc , abca },后缀 = { b , ab , cab , bcab},next[ 5 ] = 2;
KMP算法图解
KMP算法有两个关键步骤,求ne数组,KMP匹配
求ne数组和kmp很相似,相当于自己找自己的匹配串
第一个长条代表主串,第二个长条代表子串 。 蓝色表示匹配部分,绿色红色代表不匹配部分。
现在发现了不匹配的地方,根据KMP的思想我们要将子串向后移动,现在解决要移动多少的问题。
之前提到的最长相等前后缀的概念有用处了。因为蓝色部分也会有最长相等前后缀。如下图:
灰色部分就是蓝色部分字符串的最长相等前后缀,我们子串移动的结果就是让子串的蓝色部分最长相等前缀和主串蓝色部分最长相等后缀对齐。
接下来的流程就是一个循环过程了。事实上,每一个字符前的字符串都有最长相等前后缀,而且最长相等前后缀的长度是我们移位的关键,所以我们单独用一个next数组存储子串的最长相等前后缀的长度。
因此next[]的含义:下标为i 的字符前的字符串最长相等前后缀的长度为j。
习题
# include <iostream>
# include <algorithm>
# include <cstring>
using namespace std;
const int N = 1e6 + 10;
char s[N] , p[N];
int ne[N];
int main(){
int m , n;
// 下标从1开始 方便idx计算
cin >> m >> p + 1 >> n >> s + 1;
// 求next[] , i从2开始就好 ne[1]为空集
for (int i = 2 , j = 0; i <= m; i ++){
// 当j==0,无路可走,或者匹配串和模板串不匹配 , while 结束
while (j && p[i] != p[j+1]) j = ne[j];
if (p[i] == p[j+1]) j ++;
ne[i] = j;
}
// 匹配过程 , 和求ne基本一样
for (int i = 1 , j = 0; i<= n; i ++){
while (j && s[i] != p[j+1]) j = ne[j];
if (s[i] == p[j+1]) j ++;
if (j == m){
j = ne[j];//匹配成功继续往下匹配
printf("%d ", i - m);
}
}
return 0;
}