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;
}

posted @ 2022-09-12 20:56  Zilliax  阅读(57)  评论(0)    收藏  举报