算法练习题31---KMP算法


前言

KMP算法是字符串操作中非常重要的一种算法思想,主要用于指定字符串的查找。在普通的暴力查找中,时间复杂度的级别是O(NM),而KMP算法可以将时间复杂度控制在O(N+M)级别,在算法竞赛中,是一个应付时间限制非常重要的算法思想。

一、KMP简介

KMP算法的主要思想是基于暴力匹配字符串的思想,但是和暴力匹配的不同点在于,KMP会提前对进行匹配的字符串做一个处理,比如用P去S中寻找是否有能够匹配的,或者求取S中包含P的个数,那么KMP算法会先对P进行预处理,这个预处理的过程可以理解为构造一个有穷自动机,意思就是说当P中的某一位失配时,该自动机会告诉程序下一步要回到字符串P的哪一位继续匹配。

例如如下样例,(b)中采用暴力的匹配模式,而(c)中采用KMP匹配模式

可以看到,在kmp中,i 的位置是不会因为失配而回退的,而是去改变 j 的位置,这样会大大降低程序的查找用时。

关于有穷自动机的演示过程,也就是Next数组在程序中所发挥的作用,这里推荐一篇大佬博客:KMP 算法详解 - 知乎 (zhihu.com)

下面用一道例题来演示一下KMP的应用

二、例题

取自hdu 2087“剪花布条”

一块花布条,上面印有一些图案,另有一块直接可用的小饰条,也印有一些图案。对于给定的花布条和小饰条,计算一下能从花布条中尽可能剪出几块小饰条。

输入:每一行是成对出现的花布条和小饰条。#表示结束

输出:输出能从花纹布中剪出的小饰条的最多个数

输入样例:

abcde a3

aaaaaa aa

输出样例:

0

3

解析:本题是一个从S中找P的问题,正好符合KMP算法思想的要求,那么可以设定两个函数,一个是getFail()函数用于计算Next数组,另一个kmp()函数用来在S中查找P,另外根据本题的意思需要找到能分开的子串,即剪出不同的小饰条,那么可以添加一句if(i - last >= plen)进行判断。

三、程序样例

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000+5;
char str[MAXN],pattern[MAXN];
int Next[MAXN];
int cnt;
int getFail(char *p,int plen)  //预计算next
{
	Next[0]=0;  //在第一个字符失配时,回退到第一个字符
	Next[1]=0;  //在第二个字符失配时,回退到第一个字符
	for(int i=1;i<plen;i++)
	{
		int j=Next[i];  //Next[i]回退到j
		while(j&&p[i]!=p[j]) j=Next[j];  //退出的条件有两种,第一种是j=0,即回退到原始点,第二种是当回退的字符与p[i]相同时
		//如果字符回退后和p[i]相同,说明此时是一种有效回退
		Next[i+1]=(p[i]==p[j])?j+1:0;
	}
}
int kmp(char *s,char *p)
{
	int last=-1;
	int slen=strlen(s),plen=strlen(p);
	getFail(p,plen);  //预计算Next[]数组
	int j=0;
	for(int i=0;i<slen;i++)		//匹配S和P的每个字符
	{
		while(j&&s[i]!=p[j])  j=Next[j];	//失配了,用Next[]找j的回溯位置
		if(s[i]==p[j])  j++;				//当前位置的字符匹配,继续
		if(j==plen)							//完全匹配
		{
			if(i-last>=plen)				//判断新的匹配和上一个匹配是否能分开
			{
				cnt++;
				last=i;						//last指向上一次匹配的末尾位置
			}
		}
	}
}
int main()
{
	while(~scanf("%s",str))		//读串
	{
		if(str[0]=='#') break;
		scanf("%s",pattern);	//读模式串
		cnt=0;
		kmp(str,pattern);
		printf("%d\n",cnt);
	}
	return 0;
}

参考书籍:算法竞赛入门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨大熊的代码世界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值