KMP算法

KMP算法,又称模式匹配算法,能够在线性的时间内判定字符串B【1~M】是否为字符串A【1~N】的子串,并求出字符串B在字符串A中各次出现的位置和次数

一般来说,我们最常见也是最容易想到的就是暴力枚举字符串A中的每个位置i,把字符串B与字符串A的后缀A【i~N】对齐,向后扫描逐一比较B【1】与A【i】,B【2】与A【I+1】……是否相等。我们把这种比较的过程称为A与B尝试进行“匹配”。

上述的做法虽然常见,思路简单,但是时间复杂度是很高的,理想情况是O(N+M),但是很容易被卡成O(NM),且理想情况的概率是极小的,所以我们要学习KMP算法,KMP算法可以在O(N+M)的时间复杂度求出上述问题,首先记住一点就是,KMP算法是在移动模式串,而不是移动主串,也就是一直都是在移动B串

在我们的暴力做法中,可能会有很多重复操作,我们的优化就是减少重复操作来降低时间复杂度,例如:

A串为:abcdaefghiabc

B串为:abcde

在朴素算法中,我们首先从i==1开始往后逐一比较,然后发现到A[5]!=B[5](这里设字符的下标从1开始),就会回溯到i==2,然后再逐一比较,但其实这过程是重复且没用的,因为在i==1时,我们发现A[1]==B[1],A[2]==B[2],A[3]==B[3],A[4]==B[4],且B串中的前四个元素互不相同,那么就意味着B[1]和A的前四个元素也不可能相同,所以我们可以直接将B串往后移动4位,比较A【5】和B【1】,这样就等价于省去了i==2,i==3 ……开始往后遍历的情况,直接从i==5开始往后遍历,这样就省去了几步操作

再看第二种情况

A串为:bbsbbsasda

B串为:bbsbbc

首先从i ==1 开始往后遍历,在A[6]!=B[6],这个时候就不是把B串往后移动6位了,因为B[1]==B[4]==A[4],B[2]==B[5]==A[5],所以我们应该将B串往后移动4位,接着比较A[6]和B[3]

结合两种情况,我们发现移动多少位取决于B串中的前缀子串和后缀子串是否有相等的,且移动的是最大的公共前后缀子串,也就是说如果B串中有长度为3的前缀子串和后缀子串相等,也有长度为4的,我们把第4位的后面一位移到第一个不匹配的位置

我们用一个next数组存储B串中的以i结尾的非前缀子串与B的前缀能够匹配的最长长度,这样每次出现不匹配的时候就可以快速的知道移动几位,那么如何求next数组呢?

 next数组是KMP算法的核心,求出next数组就可以得到我们想要的答案了

看一个例题

P3375 【模板】KMP 字符串匹配

题目链接:https://www.luogu.com.cn/problem/P3375

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1e6 + 10;

char s[N], s1[N];
int ne_s1[N];

int main()
{
	scanf("%s%s", s+1, s1+1);
	int s_len = strlen(s + 1);
	int s1_len = strlen(s1 + 1);
	ne_s1[1] = 0;
	for (int i = 2, j = 0; i <= s1_len; i++)//求出模式串的next数组
	{
		while (j && s1[i] != s1[j + 1])j = ne_s1[j];
		if (s1[i] == s1[j + 1])j++;
		ne_s1[i] = j;
	}
	for (int i = 1, j = 0; i <= s_len; i++)//用模式串跟主串进行匹配
	{
		while (j && (j==s1_len||s[i] != s1[j + 1]))j = ne_s1[j];//要保证j大于0,等于0的时候等于将模式串移动到了第一位
		if (s[i] == s1[j + 1])j++;
		if (j == s1_len)//表示主串s中有一段区间与s1相等
			printf("%d\n", i - s1_len + 1);
	}
	for (int i = 1; i <= s1_len; i++)
		printf("%d ", ne_s1[i]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值