数据结构(串匹配—KMP算法)

KMP算法

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特–莫里斯–普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。

时间复杂度O(m+n)

KMP算法相当于对BF算法的一种优化,将BF算法中多余的比较省略掉,提高了效率
我们先来看看对比
BF算法具体讲解:https://blog.csdn.net/wfea_lff/article/details/104361318

下图为BF算法图解
在这里插入图片描述
下图为KMP算法图解
在这里插入图片描述
从上面的两张动图,我们可以看出,BF算法是将P串和S串一个一个比较,直到比出完全相同的串时才停止
而KMP算法则将这一过程优化,将一些没有必要的比较省去,提高了串匹配的效率

那如何省去不必要的比较呢?
其实这里我们做的处理是,将假设P串发生的每一位失配后,S串不动,将P串移动到相应位置上用数组记录下来,当比较的时候如果失配,则直接将P串移动到当前失配所记录的位置,然后继续比较

那如何得到失配后该移动的位置呢
我们举例说明

在这里插入图片描述
这里我们会用到next【P的大小】数组(其第一位失配后我们通常规定为-1,其第二位失配后我们通常规定为0,也就是next【0】 = -1;,next【1】 = 0;),通过上面的例子我们很容易得出结论,上图j失配后应该将P串移动成红色箭头之后的样子,很显然k = 2就是j位置失配后P串要移动的位置,即j = next【j】;

最后得出的结论的意思就是:在P串中,存在两个最长相等真子串1,一个以0位置开始,一个以j-1位置结束,k是真子串的长度

我们继续探究next的关系,我们能不能通过已知的next值来得到未知的next值呢?
比如next【j+1】 = func(next【j】),从上面的结论可以看出,k是P串两个最长且相等的真子串的长度,那我如果next【j】 = k;即j位置之前P串两个最长且相等的真子串的长度为k个长度,现在要求next【j+1】,那我就要看看这时的P【j】与P【k】是否相等,如果相等,则关系就为next【j+1】 = k+1;

那如果不等呢??
假如不等,则我们利用KMP算发,就可以看作失配,用失配时处理的方法,将k = next【k】;
这便是KMP算法的思想流程了与BF算法比起来复杂了一些,但效率提高了,算是对BF算法的一种优化

参考代码

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

void Getnext(int *next,const char *p,int len)
{
	next[0] = -1;
	next[1] = 0;
	int k,j;
	j = 1;
	k = 0;

	while(j<len-1)
	{
		if(k == -1||p[j] == p[k])
		{
			next[j+1] = k+1;
			j++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}

int KMP(const char *s,const char *p,int pos)
{
	if(s == NULL||p == NULL)
	{
		return -1;
	}
	
	int slen = strlen(s);
	int plen = strlen(p);

	if(slen<plen)
	{
		return -1;
	}

	int i,j;
	i = pos;
	j = 0;

	int *next = (int *)malloc(sizeof(int)*plen);
	if(next == NULL)
	{
		return -1;
	}

	Getnext(next,p,plen);
	for(int g=0;g<plen;g++)
	{
		printf("%d  ",next[g]);
	}
	printf("\n");

	while(i<slen&&j<plen)
	{
		if(j == -1||s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}

	free(next);

	if(j>=plen)
	{
		return i-j;
	}
	return -1;
}

int main()
{
	char *s = "aababcabcdabcdeabcababcabc";
	char *p = "abcababcabc";
	printf("%d\n",KMP(s,p,0));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值