【hiho一下】第三周 KMP算法

题目1:KMP算法

题目原文:http://hihocoder.com/contest/hiho3/problem/1

【题目解读】

就是KMP算法的基本使用之一。添加了计数功能。

KMP算法的核心思想,就是通过减少匹配失败时模式串的回溯位数,来减少匹配次数。此时回溯的时候,使用 next 数组。

求解 next 数组见:http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html,需要注意的是,这里用到了数学归纳法。即:

假设:根据定义next[0]=-1。然后,假设next[j]=k, 即P[0...k-1]==P[j-k,j-1]

则有:1)若P[j]==P[k],则有P[0..k]==P[j-k,j],很显然,next[j+1]=next[j]+1=k+1;     

    2)若P[j]!=P[k],则可以把其看做模式匹配的问题,即匹配失败的时候,k值如何移动,显然k=next[k]

next 数组的求解,可以视为相错一位的模式串,其中一个作为原始串,另外一个作为模式串,进行匹配,失败则回溯

【编写细节】

(1)想要理解KMP算法的主体比较部分,建议先实现 BF算法,就是最普通的暴力匹配。如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char ori[1000005];
char par[10005];
int next[10005];

int main()
{
	int t;
	scanf("%d", &t);
	long olen;
	int plen;
	int ans;
	while(t--)
	{
		ans = 0;
		memset(ori, 0, sizeof(ori));
		memset(par, 0, sizeof(par));
		memset(next, 0, sizeof(next));
		
		scanf("%s", par);
		scanf("%s", ori);
		
		olen = strlen(ori);
		plen = strlen(par);
		
		for(long i=0; i<olen; i++)
		{
			long j = i;
			int k = 0;

			while(ori[j] == par[k])
			{
				j++;
				k++;
				if(k >= plen) break;
			}

			if(k>=plen) ans++;
		}
		printf("%d\n", ans);
	}
	return 0;
}

对照这个再去写KMP的主题匹配,就能理解 for 里面的第一个while为了找到原始串与模式串的第一个匹配点(不一定是模式串的第一个字符);

(2)KMP里面如果不匹配就回溯(绝不用--),一律使用k = next[k],不会使用到 k--;只要回溯了第一次,之后不匹配即可不断回溯直至找到匹配或者直接到头;

(3)一定要注意!KMP算法需要单独考虑模式串与原始串长度均为1时的情况!次数 next 数组只有一个值-1;

(4)此题是KMP匹配的计数功能,所以需要原始串的一位一位字符扫描(for循环),以防遗漏ABABA中有两个ABA的情况;

(5)其余细节写在代码注释。。。很难搞,当模板记下来吧。

【AC代码】

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char ori[1000005];
char par[10005];
int next[10005];
int t, plen;
long olen, ans;

void get_next(int len)
{
	next[0] = -1;
	int j = 0;
	int k = -1;
	while(j < len)
	{
		// 一直找到可以匹配的开始,或者无法匹配的开头
		if(k==-1 || par[j] == par[k])	
		{
			// 此时保证j不能已经加过
			next[j+1] = k + 1;
			j++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}

int main()
{
	scanf("%d", &t);
	while(t--)
	{
		ans = 0;
		scanf("%s", par);
		scanf("%s", ori);
		
		plen = strlen(par);
		olen = strlen(ori);
		
		if(olen==1 && plen==1)
		{
			if(ori[0] == par[0])
				ans = 1;
			else
				ans = 0;
			
			printf("%d\n", ans);
			continue;
		}
    
		get_next(plen);
		int j = 0;
		for(long i=0; i<olen; i++)
		{
			// while的顺序:一定要先找到一个可以匹配的起点
			// j>=1杜绝出现next[j]=-1,因此单独考虑 olen=plen=1 
			while(ori[i] != par[j] && j>=1)
			{
				j = next[j];
			}
			
			if(ori[i] == par[j])
			{
				j++;
			}
			// 在找到匹配的第一个字符前,i不能继续++ 
//			else
//			{
//				j = next[j];
//			}
			
			if(j >= plen)
			{
				ans++;
				// 回溯过程中不会遗漏,最差情况从头匹配 
				j = next[j];
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值