KMP详解-hdoj2087

目录

1.KMP算法是什么?

2.KMP算法有什么用?

3.KMP实现思路

(1)暴力匹配为何低效

(3)kmp过程

4.模版代码

5.例题与源码

1.HDOJ 2087 剪花布条(模版题)


 

1.KMP算法是什么?

kmp算法是一种用于高效进行字符串匹配的算法,KMP的核心就是next数组的构造与使用.

2.KMP算法有什么用?

一般用于解决这种问题:

    涉及字符串匹配的问题.

标志性经典问题:

    现有一字符串a,一字符串b,问a串中是否包含b串,更甚至问包含多少次b串.

    解决这种字符串匹配的问题看似不难,可是如果字符串太长,比如a串有一百万长度,b串长度一万,因最简单的暴力匹配复杂度最坏为n*m,极易爆掉,故而需要优化,而KMP算法的最坏复杂度为n+m,比暴力匹配好太多了

3.KMP实现思路

(1)暴力匹配为何低效

    既然暴力匹配低效,那么必然是暴力匹配做了无意义的事,那么我们先来看看暴力匹配做了什么无意义的事.

    假设a串为1231231234,b串为1234,我们对a做暴力匹配

for(int i=0;a[i];i++){
    int j=0;
    while(b[j]&&a[i+j]==b[j]) j++;
    if(!b[j]) printf("在%d位置匹配成功",i); 
} 

    外层i=0循环时,内层循环123其实都已经匹配成功,可是此时第四个4匹配失败,a中这个位置是1,故而我们只好放弃此次配对,将i++,继续开始下一次匹配,可是明明我们看得出来,b串是1开头,23其实根本不符合,如果我们分析过b串,也许能知道在第四个匹配失败时直接i+3,这不就节约了效率吗.

    其实上面那组ab不是暴力匹配下最坏的情况,最坏的是a串为1111111111111111111111111112,b串为11111112,这种情况下暴力匹配的复杂度会达到最坏的n*m,按着暴力匹配的程序想一遍你会发现你对a串中的第七个1比较了7次,为什么会这样?因为你在b中最后那个2匹配失败时,明知道这前面的7个1是已经匹配成功的,却没有去使用这个条件.

    总结暴力匹配低效的教训,就是匹配失败时一定要充分利用已经被匹配成功的这一部分,也许你并不需要回归起点重新开始!

(2)kmp怎么去利用匹配成功的部分完成优化的

    首先介绍KMP的一个重要的小细节,kmp算法a串中每个位置只读一次,至于怎么为什么暴力匹配会读多次因为如果暴力读不每次都回到i+1的地方重新匹配是怕出现下面这种情况缺失:

        a为123123124,b为123124,虽然会在a[5]处失配,可如果直接在a[5]处重新开始与b[0]匹配,显然错了,为什么?因为a[5]失配只是从a[0]开始匹配的结果,也许以比如a[3]开始,会成功匹配,故而暴力匹配只能憋屈的在a[5]失配时回到a[1]与b[0]重新开始匹配,可到这里我们也许会发现一个小细节,在a[5]失配时,已匹配串12312与b串123124,12312的后缀12与12312的前缀12相同!为什么强调这里后面就知道了.

    现在开始介绍KMP算法实现思路:就是在匹配失败时,通过分析b串与已匹配成功的串的最长相同前后缀,来尽可能少的倒退b串的已匹配位置j.next[j]意为b中第j位置前b串的最长相同前后缀长度(真实还要减1,因为数组下标从0开始,next[j]意义其实是在i位置匹配失败则将j跳转到next[j]),当j==-1自然表示前方已匹配串没有可用的后缀,故而继续看后面的,直到某个位置b串全部匹配,则匹配成功.

(3)kmp过程

    比如上面的a串1111111111111111111111111112,b串11111112,在匹配2失败时,我们发现b的前缀与已成功的部分的后缀最长居然7位为相同,故而b串只用以b[6]继续匹配.如下图

由b串11111112构造出的next数组:

            1    1    1    1    1    1    1    2

next[]:-1    0    1    2    3    4    5    6

1111111111111111111111111112

11111112           此时i=7,j=7 失配 j=next[j]

 11111112    此时i=7,j=6 成功 i++,j++

 11111112         此时i=8,j=7 失配 j=next[j]

     11111112     此时i=8,j=6 成功 i++,j++

   1111111  此时i=9,j=7 失配 j=next[j]

思路就是上面这样子了.

4.模版代码

这个模版中kmp函数用于输出a中每个匹配b串的下标,其实kmp算法的核心只是next数组的构造,在失配时学会跟着next跳转就好.

#include <stdio.h>
const int maxn=100005;
int next[maxn];//next数组 
void getnext(char* s){//构造next数组,真正的模版
	next[0]=-1;
	int i=0,j=-1; //j为什么初值赋值-1?0其实也行,仅仅是为了少一个判断, 
	while(s[i]){
		if(j==-1||s[i]==s[j]) next[++i]=++j; 
		else j=next[j];
	}
}

void kmp(char* a,char* b){ //输出a中每个匹配b串的下标,不同问题这个函数的写法多变
	int blen=0;
	while(b[blen]) blen++;
	getnext(b);
	int i=0,j=0;
	while(a[i]){
		if(j==-1||a[i]==b[j]){
			i++,j++;
			if(!b[j]){
				printf("%d ",i-blen);
				j=next[j];  
			}
		}
		else j=next[j];  
	}
}
int main(){
	kmp("abcabcabc","abcab");
	return 0;
}

5.例题与源码

1.HDOJ 2087 剪花布条(模版题)

Problem Description

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

Input

输入中含有一些数据,分别是成对出现的花布条和小饰条,其布条都是用可见ASCII字符表示的,可见的ASCII字符有多少个,布条的花纹也有多少种花样。花纹条和小饰条不会超过1000个字符长。如果遇见#字符,则不再进行工作。

Output

输出能从花纹布中剪出的最多小饰条个数,如果一块都没有,那就老老实实输出0,每个结果之间应换行。

Sample Input

 

abcde a3aaaaaa aa#

Sample Output

 

03

题目分析:

    就是求a串中的b串个数,不过这里要注意因为是剪布条,自然不能重叠,故而我的模版代码的kmp函数需要你自己做一些修改.

    当然,一道1000数据的题你用暴力也能过,不过别忘了,你为什么看到这里.

ac代码:

#include <stdio.h>
const int maxn=100005;

int next[maxn];//next数组 
void getnext(char* s){//构造next数组,真正的模版
	next[0]=-1;
	int i=0,j=-1; //j为什么初值赋值-1?0其实也行,仅仅是为了少一个判断, 
	while(s[i]){
		if(j==-1||s[i]==s[j]) next[++i]=++j; 
		else j=next[j];
	}
}

int kmp(char* a,char* b){ 
	int ansnum=0; //最开始答案为0 
	getnext(b);
	int i=0,j=0;
	while(a[i]){
		if(j==-1||a[i]==b[j]){
			i++,j++;
			if(!b[j]){
				ansnum++; //有一个满足的就加1 
				j=0; //j直接彻底重置,避免重叠 
			}
		}
		else j=next[j];  
	}
	return ansnum; //返回答案 
}

char a[10000],b[10000];
void solve(){
	while(1){
		scanf("%s",&a);
		if(a[0]=='#'&&!a[1]) break; //判断输入结束 
		scanf("%s",&b);
		printf("%d\n",kmp(a,b));
	}
}

int main(){
	solve();
	return 0;
}


2.待加入高难度KMP题

猜你喜欢

转载自blog.csdn.net/qq_31964727/article/details/80821193