KMP算法

一、基本思想

  • s[]是文本串
  • p[]是模式串
  • 前缀:是指除最后一个字符外,字符串的全部头部组合
  • 后缀:是指除第一个字符外,字符串的全部尾部组合
  • 部分匹配值:是指前缀和后缀最长共有部分的长度
  • next[]:即为存储部分匹配值的数组
1
KMP算法的本质:在匹配失败的时候,不是将p模式串向后移一位,而是将p移动到下一个可以和前面部分匹配的位置,从而达到优化算法的目的,而决定p串如何移动到那个部分匹配位置的就是next[]数组

二、例题:KMP字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串 P 在模式串 S 中多次作为子串出现。

求出模板串 P 在模式串 S 中所有出现的位置的起始下标。

输入格式
第一行输入整数 N,表示字符串 P 的长度。

第二行输入字符串 P。

第三行输入整数 M,表示字符串 S 的长度。

第四行输入字符串 S。

输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。

数据范围
1≤N≤105
1≤M≤106
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>

using namespace std;

const int N = 1e5 + 10, M = 1e6 + 10;

char p[N], s[M];
int ne[N];//next数组,存储的是每一个下标的部分匹配值
int n, m;//

int main(){
ios::sync_with_stdio(false);
cin.tie(0);

cin >> n >> p + 1 >> m >> s + 1;//下标从1开始

//next[]即部分匹配值表
//代码与下面匹配过程的代码完全类似,不同的是,每当匹配成功的时候,需要在next中记录匹配长度
for(int i = 2, j = 0; i <= n; i++){//i = 1时,ne[i] = 0
while(j && p[i] != p[j + 1]) j = ne[j];

if(p[i] == p[j + 1]) j++;//匹配成功则将p串向后移一位

ne[i] = j;//记录成功的长度,也就是部分匹配值,也就是前缀和后缀最长的公有部分长度
//也就是,kmp匹配过程中每每匹配失败,需要将p串移动的长度

}

//kmp匹配过程
for(int i = 1, j = 0; i <= m; i++){
while(j && p[j + 1] != s[i]) j = ne[j];
//如果j不等于0(或者说j指向了一个对应的p串元素),并且s[i] != p[j + 1],那么匹配
//失败,通过next数组移动p串。如果仍然匹配失败,那么继续移动直到匹配或p串移动到i的后面(j = 0)
if(s[i] == p[j + 1]) j++;//如果能匹配成功,则p串向前移动一位

if(j == n){ //匹配成功

cout<<i - n<<" ";//若下标从1开始,加一,也就是 i - n + 1

j = ne[j]; //j此时指向的是模式串的最后一个字符,需要将j重置到0,
//然后继续匹配下一个子串
}
}
return 0;
}