kmp算法详解 c语言

/*
介绍kmp算法前,不妨先了解这样一个数组:next数组
next[i]的值是 取字符串S的前i个字母组成的字符串s 的"最长公共前后缀"的长度
先解释前缀与后缀的含义:字符串"abcd"有三个前缀:a,ab,abc
有三个后缀:bcd,cd,d
例如,有字符串S=“aabaaaf”,
i=1时, s=“a”, 没有前后缀, next[1]=0;
i=2时, s=“aa”, 前缀"a"与后缀"a"公共长度为1, next[2]=1;
i=3时, s=“aab”, 前缀"a"或"aa"与后缀"b"公共长度均为0, next[3]=0;
i=4时, s=“aaba”, 前缀、后缀均取"a"时,公共前后缀的长度最长, next[4]=1;
i=5时, s=“aabaa”, 前缀、后缀均取"aa"时,公共前后缀的长度最长, next[5]=2;

那么,这个next数组有什么用呢?

假设有两个字符串,
这里写图片描述
如果用传统的匹配算法,'d’与’f’不匹配(前面都正确匹配),那么s字符串向右移动一位:
这里写图片描述
'b’与’a’不匹配,s字符串再向右移动一位:
这里写图片描述
就这样一直移动,直到完全匹配。
这里写图片描述
显然,这种匹配算法容易理解但很耗时。

我们仔细观察这两个字符串:
这里写图片描述
S字符串与s字符串的第六个字母不匹配,也就是它们的前五个字母相同,
而对于s字符串的next数组,next[5]=2,
也就是说,s字符串的第一、二个字母与第四、五个字母相同,
而s字符串的第四、五个字母又与S字符串的第四、五个字母相同,
那么,s字符串的第一、二个字母必然与S字符串的第四、五个字母相同!!!
所以,当S字符串与s字符串的第六个字母不匹配时,我们不必一次只移动一位,
可以直接移动到如下位置(s字符串的第一、二个字母与S字符串的第四、五个字母相对应):
这里写图片描述
事实上,当S[i]!=s[j]时,令j=next[j],继续比较S[i]与s[j],可以达到一次移动多位的效果。
(因为next[i]是 取字符串前i位组成的新字符串 的最长公共前后缀的长度,
那么next[i]就是这个前缀的下个位置的下标
例如aabaa,公共前后缀长度最长时,
前缀、后缀均为"aa",2是next[5]的值,同时是前缀"aa"下一个位置’b’的下标)

例如上例中,S[6]!=s[6](i=6,j=6),令j=next[j]( j = 2 ),这时比较的是S[6]与s[2]。

现在,我们已经感受到了next数组的“神通广大”,那么问题来了,如何求next数组?

显然:next[0]没有意义,而next[1]=0

对于i>1,如果s[i]==s[next[i]],那么s[i]=next[i]+1。
证明:s=“aabaaba”,i=5时,next[5]=2,说明前5个字母组成的字符串有长度为2的公共前后缀。如果该前缀的下一个字母与该后缀的下一个字母相同,那么:next[6]=next[5]+1=3

但是,如果s[i]!=s[next[i]]呢?(个人认为,这里是kmp算法中最难理解的地方)
假如有s=“aabbaaa”,i=6时,next[6]=2,s[6]!=s[next[6]],
但是,我们可以看到,s[5]==s[0],如果s[6]==s[1],那么next[7]=next[1]+1。
那么,怎么利用s[5]==s[0]这个条件呢?当然是利用我们的next数组。
假设有字符串s,next[i]=m,s[i]!=s[next[i]]。
由next[i]=m可以推出:由s字符串的前i个字母组成的字符串,存在长度为m的相同的前缀和后缀,
我们设该前缀和后缀分别为s1和s2,假如next[m]=k,
那么,由s字符串的前m个字母组成的字符串(也就是s1),存在长度为k的相同的前缀和后缀,
我们设该前、后缀分别为s11和s12,由于s1与s2相同,在s2同样可以找到s21与s22,
使得s21与s22长度为k且相同。事实上,s11=s12=s21=s22。
我们根据s11=s22可知:s[i]前面的k个字母(也就是s22),
与s字符串的前k个字母(也就是s11)相同,所以,如果s[i]==s[k],那么next[i+1]=k+1。
也就是说:假如j=next[i],如果s[i]!=s[j],我们可以令j=next[j],继续比较s[i]与s[j],
如果相等,next[i+1]=j+1,
如果不等,令j=next[j],一直循环直至j<=0 (j<=0时next[j]没有意义)

得到next数组之后,就是S与s的匹配,这与求next数组的思路差不多,大家看代码自行体会。
*/

#include<stdio.h>
#include<string.h>
const int maxn=20;
int next[maxn];
void getNext(char *s){
	int len=strlen(s);
	int j=0;
	for(int i=1;i<len;i++){
		while(j>0&&s[i]!=s[j])
			j=next[j];
		if(s[i]==s[j])
			j++;
		next[i+1]=j;
	}
}
int kmp(char *S,char *s){
	int len1=strlen(S);
	int len2=strlen(s);
	int j=0;
	for(int i=0;i<len1;i++){
		while(j>0&&S[i]!=s[j])
			j=next[j];
		if(S[i]==s[j])
			j++;
		if(j==len2){
			return i-j+1;//如果匹配成功,返回第一个匹配位置
		}
	}
	return -1;//匹配不成功返回-1
}
int main(){
	char s[maxn],S[maxn];
	while(scanf("%s%s",S,s)!=EOF){
		memset(next,0,sizeof(next));;
		getNext(s);
		int ans=kmp(S,s);
		printf("%d\n",ans);
	}
	return 0;
}

运行结果:
ABCDEFG EF
4
ABCDEFG EE
-1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值