KMP
概念
这是一个字符串匹配算法,对暴力的那种一一比对的方法进行了优化;
基本概念
1、s[ ]是模板串,即比较长的字符串。
2、p[ ]是模式串,即比较短的字符串。
3、“非平凡前缀”:指除了最后一个字符以外,一个字符串的全部头部组合。
4、“非平凡后缀”:指除了第一个字符以外,一个字符串的全部尾部组合。(后面会有例子,均简称为前/后缀)
思路
核心思想
比较抽象,很难理解,建议直接看举例
在每次失配时,如果本次失败有相同的前缀或后缀,则下次匹配将选择最长的一段进行匹配。
而每次p串移动的步数就是通过查找next[ ]数组确定的。
next[]数组
对next[ j ] ,是p[ 1 ~ j ]串中前缀和后缀相同的最大长度(部分匹配值),即 p[ 1 ~ next[ j ] ] = p[ j - next[ j ] + 1 ~ j ]。
举例理解_简单
s : a b c a b c a b d d
p : a b c a b d d
第一轮
————————————————————————————————
a b c a b c a b d d
a
————————————————————————————————
a b c a b c a b d d
a b
————————————————————————————————
a b c a b c a b d d
a b c
————————————————————————————————
a b c a b c a b d d
a b c a
————————————————————————————————
a b c a b c a b d d
a b c a b
————————————————————————————————
a b c a b c a b d d
a b c a b d(X)
————————————————————————————————
寻找a b c a b共有的最长前缀和后缀进行下一轮匹配;
很显然是a b;
第二轮
————————————————————————————————
a b c a b c a b d d
X X X a b
————————————————————————————————
a b c a b c a b d d
X X X a b c
————————————————————————————————
a b c a b c a b d d
X X X a b c a
————————————————————————————————
a b c a b c a b d d
X X X a b c a b
————————————————————————————————
a b c a b c a b d d
X X X a b c a b d
————————————————————————————————
a b c a b c a b d d
X X X a b c a b d d
————————————————————————————————
匹配
KMP就两种操作
求next数组和匹配字符串
先讲匹配字符串
建议s和p都从下标为1开始,i从1开始,j从0开始,每次拿s[i]和p[j+1]开始作比较;
而当字符串匹配到s[ a ~ b ] = p[1 ~ j] && s[i] != p[j + 1]的时候;
此时移动p串,使j = next[j];
重复以上过程,直到s或者p其中任意一个读到末尾;
next数组构建
next数组实际上是代表了在匹配失败后,字符串可以跳过匹配字符的个数;
比如上面的匹配例子,第一轮匹配成功的a b c a b的情况来说,这5个字符,从前往后看和从后往前看(但是仍然从左往右读),都有一个共同并且是最长的前后缀a b;
举例说明_更加具体
单独设一个p串 a b a c a b a b
1.从第一个a开始
当元素个数为1时,不存在前后缀,
所以第一个a的next为0;
—————————————————————————————————————————————————
2.到元素个数为2时,
前缀为a;
后缀为b;
不存在相同,此时a(下标为1)和b(下标为2)的next都为0;
—————————————————————————————————————————————————
3.元素个数为3,
前缀为a , a b ;
后缀为a , b a;
此时a(下标1)与a(下标3)匹配,长度为1,所以a(下标3)的next为1;
—————————————————————————————————————————————————
4.元素个数为4,
前缀为a , a b , a b a;
后缀为c,a c , b a c;
此时没有能匹配的前后缀,所以c的next为0;
————————————————————————————————————————————————
5.元素个数为5.
前缀为a , a b , a b a , a b a c;
后缀为a , c a , a c a, b a c a;
只有两个a匹配到了,长度为1,所以a(下标5)的next为1;
————————————————————————————————————————————————
6.元素个数为6,
前缀为a , a b , a b a, a b a c , a b a c a;
后缀为b , a b , c a b , a c a b , b a c a b;
a b匹配到了,长度为2,所以b(下标6)的next为2;
—————————————————————————————————————————————————
7.元素个数为7,
前缀为a , a b , a b a , a b a c , a b a c a , a b a c a b;
后缀为a , b a , a b a , c a b a , a c a b a , b a c a b a;
a b a匹配到了,长度为3 , 所以a(下标7)的next为3;
————————————————————————————————————————————————————
8.元素个数为8
略
自己写写,就会发现a b匹配到了,所以b(下标8)的next为2;
————————————————————————————————————————————————————
总结以上会发现
| p串 | a | b | a | c | a | b | a | b |
|---|---|---|---|---|---|---|---|---|
| next | 0 | 0 | 1 | 0 | 1 | 2 | 3 | 2 |
例题
https://www.acwing.com/problem/content/description/833/
给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模式串 P 在字符串 S 中多次作为子串出现。
求出模式串 P 在字符串 S 中所有出现的位置的起始下标。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010, M = 1000010;
int n, m;
char p[N], s[M];
int nx[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
//预处理nx
for (int i = 2, j = 0; i <= n; i++)
{
while (j && p[i] != p[j + 1])//当已经匹配到至少一个字符时,查看下一个字符是否匹配;
{
j = nx[j];//如果不匹配,就等于那个没有匹配成功的字符的nx;
}
if (p[i] == p[j + 1])//如果发现字符相同;
{
j++;//查看下一个字符
}
nx[i] = j;//储存nx[i];
}
//KMP匹配
for (int i = 1, j = 0; i <= m; i++)//i枚举s , j枚举p;
{
while (j && s[i] != p[j + 1])//j没有推到终点就无法匹配;
{
j = nx[j];
}
if (s[i] == p[j + 1])//匹配成功
{
j++;//移动到下一位
}
if (j == n)//p完全与s的一部分字符匹配成功
{
cout << i - n << ' ';//匹配成功的下标;
j = nx[j];//移动
}
}
return 0;
}

浙公网安备 33010602011771号