字符串匹配算法之---KMP算法

KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。

比如2个字符串

S1: babababccb
S2: ababc

现在,我们想要知道,S2这个字符串在S1这个字符串中存在吗?如果存在,请输出所在位置。

->通过肉眼观察,我们很容易发现S2在S1中存在,位置处于4.

在不知道kmp算法之前,我们在处理的时候,采用移位的思想,首先比较2个字符串的第一个字母,如果不相同,那么就移动一位。如果相同,继续比较下面几个字符串,知道S2的所有字符串都相同,那么就匹配成功了。

下图就是模拟传统匹配的过程:

在这里插入图片描述

这样的匹配看起来也可以实现我们刚刚的功能,但是每次都要去比较整个串,时间复杂度上是不允许的。

可以举一个极端的例子:

S1:aaaaaaaaaaaaaaaaab
S2:aaaaaab

如果是这个例子,S2在最后匹配成功,前面很多无用的比较都是浪费时间的。时间复杂度为O(n*m)

这时候,kmp就上场了,kmp就是对传统的暴力算法的优化,传统右移每次移动1位,而kmp很聪明,每次移动若干位。

那么,如何实现每次尽可能的多移动位数,从而达到最快速度匹配呢。

kmp在匹配之前,首先计算一个Next数组。这个next数组计算的是,找出最长的相同前缀和后缀。

字符串S2:ababc

子串有:

a 最长的相同前缀和后缀:0
ab 最长的相同前缀和后缀:0
aba 最长的相同前缀和后缀:1
abab 最长的相同前缀和后缀:2
ababc 最长的相同前缀和后缀:0

Next[]={0,0,1,2,0};

求next数组的代码:

void get_next()
{
	_next[0]=-1;
	//第一个值为-1的意思就是,在上面数组的基础上,都向后偏移一位
	//第一个位置填-1,这样写方便后面匹配。
	//结果为 [-1,0,0,1,2]
	int i=0;
	int j=-1;
	while(i<m)
	{
		if(j==-1||t[i]==t[j]) //t可以看做上面的S2
		{
			i++;
			j++;
			_next[i]=j;
		}
		else
		{
			j=_next[j];
		}
	}
}

S2=“ababc”
第一次 i=0,j=-1 -> i++,j++,next[1]=0
第二次 i=1,j=0 -> j=next[j] -> j=-1
第三次 i=1,j=-1 -> i++,j++,next[2]=0
.
.
.
.这样模拟下去,就能计算出next数组,不清楚的手工模拟一下就懂了。

接下来就是匹配过程,一张图来解释:

在这里插入图片描述

模板题 HDU1711

AC代码+kmp模板:

#include <bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<(y);i++)
using namespace std;
int const N=1000000+100;

int T,n,m;
int s[N],t[N];
int _next[N];

void get_next()
{
	_next[0]=-1;
	int i=0;
	int j=-1;
	while(i<m)
	{
		if(j==-1||t[i]==t[j])
		{
			i++;
			j++;
			_next[i]=j;
		}
		else
		{
			j=_next[j];
		}
	}
}
int kmp()
{
	get_next();
	int i=0;
	int j=0;
	while(i<n)
	{
		if(j==-1||s[i]==t[j])
		{
			i++;
			j++;
		}
		else
		{
			j=_next[j];
		}
		if(j==m)return i-j+1;
	}
	return -1;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		rep(i,0,n)scanf("%d",&s[i]);
		rep(i,0,m)scanf("%d",&t[i]);
		printf("%d\n",kmp());
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值