KMP算法学习笔记

怎么突然从图论跳到字符串了。。。

UPDATE 2021 - 08 - 23

KMP 练习题:

P3435 [POI2006]OKR-Periods of Words

P3426 [POI2005]SZA-Template

题解博客:P3426 [POI2005]SZA-Template (题记)

P4391 [BOI2009]Radio Transmission 无线传输

P2375 [NOI2014] 动物园

定义

KMP 算法是用来处理字符串匹配问题的。

话说这个算法名还是提出它的三个人的姓首字母拼成的。。。

基本问题:

给你两个字符串,需要你回答,B 串是否是 A 串的子串。

我们称 A 串为母(主)串,用来匹配的 B 串为模式串。

暴力的时间复杂度是 O ( n m ) O(nm) O(nm),但是 KMP 的时间复杂度是 O ( n ) O(n) O(n) (假设 m ≤ n m \leq n mn

代码很短,但是思路较为复杂。

算法流程

先来模拟一下暴力的流程:

用两个指针分别指向母串和模式串,按位比较,

如果失配(即 a [ i ] ! = b [ j ] a[i] != b[j] a[i]!=b[j]),

那么我们的 i 回到刚才起始位加一的地方,j 回到 1(重新再匹配一次)。

这样很明显会超时,那么我们就来优化重新匹配所需的时间(在已失配的情况下)


ps:接下来所有字符串默认下标从 1 开始,写代码时直接 scanf("%s", a + 1); (a 为 char 类型的)即可。

KMP 的主要思想就是:

失配后 j(指向模式串的指针)不回到 1,而是回到某个位置,

使得能少匹配很多不必要的字符。

我们不对 i(指向母串的指针)做改变,只对 j 的指向位置进行优化。

因此,不论失配与否,i 都加一,跳到母串的下一个字符(即我们接下来的操作都是针对模式串的)。

那 j 要怎么跳才能优化次数呢?

先给出一个模式串:

a b c d a b d。

假若我们之前和母串都匹配成功,直到我们匹配到第七个字符——d。

注意:

j 此时是指向 6 的,我们每次匹配都是对 j + 1 j+1 j+1 进行匹配,

也就是每次指向将要匹配的字符——这样若失配了,我们就可以直接跳到 k m p j kmp_j kmpj j + 1 j+1 j+1 失配后指向的下标)。

言归正传,我们在匹配到第七个时失配了:

  1. 暴力:j 重新指向 1;

  2. KMP:j 指向 3。

暴力不说了, KMP 为什么要指向 3?

若前 1 到 6 个都匹配成功了,我们要重新开始匹配了,

可以看见, b j = b 2 b_j = b_{2} bj=b2 b j − 1 = b 1 b_{j-1} = b_{1} bj1=b1 j ← 6 j \gets 6 j6 i ← 7 i \gets 7 i7),

我们就抽象地想象把模式串整体移位,让 1 对准 j − 1 j-1 j1

后面依次和母串对齐,继续匹配。

这样做,可以发现,我们倒数二三位既然已经匹配成功了,

那么我们就不需要再次匹配,接下来让 c 和 a i a_{i} ai 匹配即可。

这样就可以保证,a 串不需要重复匹配,每次移动 j 即可。


通过上述举例推论,发现:

每次若 j + 1 j+1 j+1 失配了,我们就跳到除去 j + 1 j+1 j+1 位最长相同的前缀后缀的长度加一的位置。

(若前缀后缀相同,即不用再匹配前缀了,直接匹配不同的即可。)


举个例子(下标从 1 开始):

模式串:a b a a b a c;

kmp 数组:0,0,1,1,2,3,0。

代码实现

kmp 数组

kmp 数组存储每次 j + 1 j+1 j+1 失配后要跳到哪里(包含 j 的最长前后缀长度)。

求 kmp 的过程就是模式串自己和自己匹配的过程。

这个的过程就和上述的 KMP 算法过程相同了。

(模式串下标从 1 开始。)

第一位不需要寻找 kmp 值,我们循环从 2 到 lenb——每次都是计算 i 的 kmp 值。

一样地,我们有一个下标 j 指向“模式串”(此时母串和模式串都是 b 串)。

然后每次当 j + 1 j+1 j+1 失配后,直接跳到 k m p j kmp_j kmpj 即可,

后面匹配还是拿 j + 1 j+1 j+1 匹配。

最后如果匹配成功,即 k m p i + 1 ← + + j kmp_{i+1} \gets ++j kmpi+1++j

代码:

	for (int i = 2, j = 0; i <= lenb; i++)
	{
		while (j and b[i] != b[j + 1]) j = kmp[j];
		if (b[i] == b[j + 1]) j++;
		kmp[i] = j;
	}

求模式串于母串的位置

和求 kmp 数组相似,它是拿母串和模式串匹配(上述的是模式串自我匹配)。

唯一需要注意的是:

  1. 从第 1 位匹配到第 lena 位;

  2. 若匹配成功,记得 j 需要回到 k m p j kmp_j kmpj,继续匹配(具体因题目要求而定)。

代码:

	int j = 0;
	for (int i = 1; i <= lena; i++)
	{
		while (j and b[j + 1] != a[i]) j = kmp[j];
		if (b[j + 1] == a[i]) j++;
		if (j == lenb)
		{
			printf ("%d\n", i - lenb + 1);
			j = kmp[j];
		}
	}

总体来说,KMP 的思维难度确实较大,

但是可以通过多刷 KMP 的题,找找关于 kmp 数组的感觉。

P3375 【模板】KMP字符串匹配

代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 1000010;
int kmp[maxn];
int lena, lenb;
char a[maxn], b[maxn];

int main ()
{
	scanf ("%s %s", a + 1, b + 1);
	lena = strlen (a + 1), lenb = strlen (b + 1);
	for (int i = 2, j = 0; i <= lenb; i++)
	{
		while (j and b[i] != b[j + 1]) j = kmp[j];
		if (b[i] == b[j + 1]) j++;
		kmp[i] = j;
	}
	int j = 0;
	for (int i = 1; i <= lena; i++)
	{
		while (j and b[j + 1] != a[i]) j = kmp[j];
		if (b[j + 1] == a[i]) j++;
		if (j == lenb)
		{
			printf ("%d\n", i - lenb + 1);
			j = kmp[j];
		}
	}
	for (int i = 1; i <= lenb; i++) printf ("%d ", kmp[i]);
	return 0;
}

—— E n d End End——

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值