kmp算法,小试牛刀

KMP算法

简述

​ kmp用来返回主串中模式串的所有位置,即数组下标。在暴力匹配时,每次匹配失败会从新返回到主串的下一个字符进行比较,时间复杂度很高,在实际生活中不实用。后来有人发现如果在匹配失败时,当前匹配成功的字符有重合的前缀和后缀字符时,可以在遍历主串时,通过重合模式串的前缀和主串的后缀的方式来减少匹对次数,(具体减少的是主串的比较次数)具体如图过程所示。
在这里插入图片描述
在这里插入图片描述

过程

看毛片儿算法主要有两个过程,一是主串的遍历,二是生成前缀码

####生成最长可匹配前缀码(prefix数组)

这里主要用到了动态规划和回溯的思想,真的很强原理如下:

  • next定义为当前位置之前的最长可匹配前缀,所以从下标1开始进行遍历,而第一个位置赋为-1.
  • 当遍历过程中,如果动态规划断掉了,需要进行回溯。如何回溯?——回到之前匹配好的最长可匹配前缀当中去寻找最长可匹配前缀的最长可匹配前缀!

在这里插入图片描述

代码

  1. 用一个变量len来储存之前已匹配的最长可匹配前缀码的下标,初值为0.

  2. prefix数组第一个值赋为-1,第二个元素赋值为0,表示当i==2时,没有一个字符可以和第一个字符对比,所以赋值为0

  3. 第二个字符和第一个字符进行对比,如果成功,len++;*(prefix+i)=len;i++,即最长可匹配前缀加一,赋值给prefix数组,然后下标后移。

  4. 如果失败,回溯之前匹配好的最长可匹配前缀当中去寻找最长可匹配前缀的最长可匹配前缀,但因为以前已匹配好的最长可匹配前缀数量在它的长度的下一个元素的值,于是len=*(prefix+len);

  5. 最后会出现一个小问题,如果*(prefix+len)的值为0时,len会被赋值为0,如果此时当前字符和这个0位置的字符不同时,len会被赋值为-1,于是越界。所以len必须大于0.

int *getNext(String *pattern){
	int *prefix=malloc(sizeof(int)*pattern->length);
//	当前位置之前已匹配的最长可匹配前缀 
	*prefix=-1;
	*(prefix+1)=0;
	int len=0;
	int i=2;
	while(i<pattern->length){
		if(*(pattern->string+i-1)==*(pattern->string+len)){
			len++;
			*(prefix+i)=len;
			i++;
		}else{
			if(len>0){
				len=*(prefix+len);
			}else{
				*(prefix+i)=len+1;
				i++;
			}
		}
	}
	return prefix;
}

测试代码如下:

typedef struct{
	char *string;
	int length;
}String;
void test(){
	String *str=malloc(sizeof(String));
	str->string="ABABABABCABAAB";
	str->length=14;
	String *pattern=malloc(sizeof(String));
	pattern->string="ABABCABAA";
	pattern->length=9;
	int *array=getNext(pattern);
	int i;
	for(i=0;i<pattern->length;i++){
		printf("%d\n",*(array+i));
	}
}

运行结果如下:
在这里插入图片描述


遍历主串进行匹配

我们应该从暴力匹配的前提下来思考这个问题具体描述如下:

  • 场景描述:在进行比对时,当比对到一半,主串下一个字符突然和模式串的下一个字符突然不一样了,此时我们所需要重合的模式串前缀和主串后缀是当前字符的前面一个字符的最长已匹配前缀,这也是动态转移方程为什么会这样定义的原因。
  • 先获取next数组int *prefix=getNext(pattern);
  • 从0开始遍历,匹对成功,j的大小刚好等于模式串的长度,并且模式串最后一个字符和主串相同时,匹对成功,返回模式串的起点return i-j;
  • 为了匹对完主串,还是需要将模式串的前缀和主串的前缀进行重合。
  • 如果正在匹配,还没有失败,两字符相等时,下标各自前进一位。
  • 如果匹对失败,将模式串的前缀和主串的前缀进行重合。
void kmp(String *text,String *pattern){
	int *prefix=getNext(pattern);
	int i=0,j=0;
	printf("tag1\n");
	while(i<text->length){
		if(j==pattern->length-1&&*(text->string+i)==*(pattern->string+j)){
			printf("Found pattern at %d\n",i-j);
			j=*(prefix+j);
		}
		if(*(text->string+i)==*(pattern->string+j)){
			++i;
			++j;
		}else{
			j=*(prefix+j);
			if(j==-1){
				++i;
				++j;
			}
		}
	}
	free(prefix);
}

typedef struct{
	char *string;
	int length;
}String;
void funcIndex1(){
	String *str=malloc(sizeof(String));
	str->string="ABABABABCABAAB";
	str->length=14;
	String *pattern=malloc(sizeof(String));
	pattern->string="ABABCABAA";
	pattern->length=9;
	int *array=getNext(pattern);
	kmp(str,pattern);
}

在这里插入图片描述


注:以上图片有的是借鉴别人的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值