C语言 数据结构与算法 KMP

1、KMP简介:

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。字符串的模式匹配实质上就是寻找模式串P是否在主串T 中,且其出现的位置。我们对字符串匹配的效率的要求越来越高, 应不断地改良模式匹配算法,减少其时间复杂度。可以应用于网络信息查找、文本中字符串的索引等。

2、KMP算法介绍:

(以下为个人理解,不保证所有人都理解我的学习想法,如果看不懂next优化及创建过程的文章末尾有详解链接)

已知两个字符串S,T;S为匹配串,T为模式串,要在S中找到是否有T,步骤如下:

假设现在S串匹配到 i 的位置,T串匹配到 j 的位置;                                                                                                                                  如果当前字符匹配成功(S[i]=T[j]);则继续匹配下一个字符 ;(i++,j++)                                                                                            如果当前字符匹配失败(S[i]!=T[j]);将模式串T的 j 标记移动到j-next[j]位继续匹配,i 标记位置不用改变;

(比较于暴力匹配引出next数组)对于KMP算法的核心就是制造next数组;可以使得暴力匹配提高效率:举一个例子:

如果S串为"aaaaaaab",T串为"aaaab",我们以暴力匹配的做法,在i=4,j=4时S[4]='a',T[4]='b',此时S[5]!=J[5];我们就需要将i,j标记向后移动一位再次去匹配,但事实上,由于T串的特殊性即T[0-3]='a',在第一次匹配中,S和T中前四个字符都相等了(‘a’),其实没必要再去反复的比较S[1~3]与T[0~3]的字符了。因此,我们就需要一个next数组来标记当S与T不匹配时,应该把T中的哪一个元素继续与i比较。

3、next[ ]:

对于每一个模式串我们会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠,我们举一个一般性的例子,假设T='abaabcaba';

在T中我们要找到当前字符T[j]的最大相等的前缀与后缀,长度为(k);

模式串abaabcaba
最大前后缀相等元素长度:001120123

例如:T[8] = 'a';它的前缀是T[0-2]='aba',后缀是T[6-8]='aba',结果为3;

但是对于最长的相同的前缀和后缀,我们要清楚在S与T不匹配时,标记J具体要移动多少,所以通过上面找最长的相同的前缀和后缀的方式,我们创建next数组。

模式串Tabaabcaba
j012345678
next[j]-100112012

next数组的创建:

#define imax 105
int next[imax];
void getnext(char *p)
{
	int lenp=strlen(p);
	next[0]=-1;
	int k=-1;
	int j=0;
	while(j<lenp-1)
	{
		if(k==-1||p[j]==p[k])	//找到前缀最长匹配度
		{
			j++;
			k++; 
			if(p[j]!=p[k])	//如果当前元素在前缀中不存在时,
			{
				next[j]=k;
			}	
			else 
		    	next[j]=next[k];	//KMP优化
		}
		else
		{
			k=next[k];
		}
	}
	return;
}

4、匹配过程:

还是以上面为例,如果S串为"aaaaaaab",T串为"aaaab",那么我们知道next[ ]数组应该为{-1,0,1,2,3};在i<strlen(S),j<strlen(T)的情况下,我们比较S与T,当i=4,j=4时;S[4]!=T[4];我们按照算法i不变,j=next[j] = 3,比较:S[4]=T[3]='a',S[5]!=T[4],j=next[j]=3;    S[5]=T[3]='a',S[6]!=T[4],j=next[j]=3;        S[6]=T[3]='a',S[7]=T[4]='b';  j=strlen(T);完成。也就是说在i从5-7时,一直在比较S中是否有‘ab’。匹配成功时,我们要求返回位置。

KMP匹配:

int KMP(char *s,char *p)
{
	int i=0;
	int j=0;
	int lens=strlen(s);
	int lenp=strlen(p);
	while(i<lens&&j<lenp)
	{
		//如果j=-1(表示第一次开始或者重新开始匹配),即失配于首字符
		if(j==-1||s[i]==p[j])
		{
			j++;
			i++;
		}
		else
		{
			//如果j!+-1,或者当前匹配失败 ,则 
			j=next[j]; // i不变,j=next[j] 
		}
	}
	if(j==lenp)
		return i-j;//匹配成功 返回位置
	else
		return -1; 
}

5、测试:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
	int t,i;
	char a[10005],b[10005];
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s",a);
		scanf("%s",b);
		if(strlen(a)>=strlen(b))
		{
			getnext(b);
			if(KMP(a,b)>-1)
				printf("yes:%d\n",KMP(a,b));
			else
				printf("no:%d\n",KMP(a,b));
		}
		else
		{
			getnext(a);
			//getnext(b);
			if(KMP(a,b)>-1)
				printf("yes:%d\n",KMP(a,b));
			else
				printf("no:%d\n",KMP(a,b));
		}
		for(i=0;i<strlen(b);i++)
		{
			printf("%d ",next[i]);
		}
	}
	return 0;
}

附上一篇精致的解析KMP博客:

https://blog.csdn.net/v_july_v/article/details/7041827#t9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值