数据结构 4 字符匹配-KMP算法

第四章主要介绍的是串,但是串的实现没什么必要,最重要的知识点在于KMP算法的使用,大二时数据结构总结过一次KMP算法,大二时总结的比较细致,链接如下:
https://blog.csdn.net/weixin_43849505/article/details/88990526

在此基础上补充一下新的理解,这个算法真的很抽象,有些地方确实表达不清楚。

KMP算法主要是为了解决匹配过于暴力的办法,方便叙述,将待检测的称为查找串,将作为标准的称为模式串。
数据结构 4 字符匹配-KMP算法_第1张图片
一般的模式匹配都是暴力匹配,即拿着模式串和查找串一个一个对,如果不匹配,查找串跳到下一个位置,模式串从头开始,这样做必然简单,但是当匹配不成功次数很多且每次匹配不成功都出现在模式串偏后的位置时,暴力匹配就很浪费时间,基于此,创造了KMP(看毛片)算法,主要思路就是如果到了模式串后面才出现了不匹配,那么前面必然是已经匹配的,如果在这部分已经匹配的里面有头上已经出现过的,那么就可以少匹配一段。

这么说很抽象,举个例子:模式串为1 2 3 1 1 2 3。假设在第七位上不匹配了,那么前面6位是已经匹配的,不难发现,五六位和一二位是一样的,如果再次从头匹配,必然要经过很长的过程才会让原来查找串中与五六位对应的位置和一二位进行匹配,而我们知道这个过程肯定不会有匹配,所以可以直接让模式串跳到第三位再继续与查找串匹配。

基于KMP算法,还有一个优化版本,还是上面那个例子,如果第七位不匹配,应该与第三位匹配,但第七位和第三位都是3,也就是说不和第七位匹配的必然也不会和第三位匹配,所以就没有必要再比较这一次,直接说明当前位置没有匹配的,应该从下一个位置开始。这种优化就是nextval优化。

放一下代码模板:

#include
using namespace std;
string str;
string mode;
int next[105];
int nextval[105];

void getnext()
{
	int i=1,j=0;
	next[1]=0;
	while(i<mode.length())
	{
		if(j==0||mode[i]==mode[j])
		{
			i++;
			j++;
			next[i]=j;
		}
		else
			j=next[j];
	}
}
void getnextval()
{
	int i=1,j=0;
	nextval[1]=0;
	while(i<mode.length())
	{
		if(j==0||mode[i]==mode[j])
		{
			i++;
			j++;
			if(mode[i]!=mode[j])
				nextval[i]=j;
			else
				nextval[i]=nextval[j];
		}
		else
			j=nextval[j];
	}
}
bool kmp()
{
	int i=1,j=1;
	while(i<=str.length()&&j<=mode.length())
	{
		if(j==0||mode[j]==str[i]){
			i++;
			j++;
		}
		else
			j=next[j];
	}
	if(j>mode.length())
		return true;
	else
		return false;
}
int main()
{
	cin>>mode>>str;
	mode=" "+mode;
	str=" "+str;
	getnext();
	getnextval();
	if(kmp())
		printf("YES\n");
	else
		printf("NO\n");
	return 0;
 } 

说明一下里面的几个点:
①查找串和模式串都从下标1开始,所以在输入后都补了一个空格在字符串前面。
②getnext函数就是为了获得next数组,获得过程中,next[1]应该等于0,因为当模式串的第一个字符就和查找串不一样,说明肯定不会有在当前位置找到匹配的,应该让查找串移动到下一个位置,而模式串不需要做任何操作,这样子就可以让模式串的第一个字符与查找串的下一个位置开始进行匹配。
③getnext主要就是查找后缀和前缀是否有重叠部分,语言叙述一下这个过程,顺序访问模式串,next数组只需要模式串就可以确定,i表示当前模式串所在位置最后一个元素,j表示已匹配前缀的下一个位置的元素(1~j-1和i-j+1~i-1已经匹配),如果i和j位置上元素相等,那么在此基础上加一即为next数组对应位置的值,特判的j=0是说,当前缀后缀根本没有相等部分,只能让模式串从头开始,即跳到1的位置上。前缀后缀有相等部分但是当前位置不相等时,说明这个前缀已经不能用了,需要回跳,跳回到next[j]的位置,这里不是很好说明,next数组记录的是到某一位如果不匹配应该从模式串的哪里开始继续比较,前后缀不匹配了,和前缀与模式串不匹配实际上是一样的,应该让后缀再与之前已经匹配的前缀再开始比较。

while(i<mode.length())
	{
		if(j==0||mode[i]==mode[j])
		{
			i++;j++;
			next[i]=j;
		}
		else
			j=next[j];
	}

④nextval数组只是在next数组的基础上做了点修改,加了一个判断,其余的部分是一样的,使用时需要修改kmp函数的比较数组。

void getnextval()
{
	int i=1,j=0;
	nextval[1]=0;
	while(i<mode.length())
	{
		if(j==0||mode[i]==mode[j])
		{
			i++;
			j++;
			if(mode[i]!=mode[j])
				nextval[i]=j;
			else
				nextval[i]=nextval[j];
		}
		else
			j=nextval[j];
	}
}

⑤kmp函数中,i指向的是查找串,j指向的是模式串。j=0表示第一个字符就和查找串不一样,直接移动查找串然后和模式串第一个字符开始比较。如果相等就都移动到下一个位置继续比较,否则模式串调整到对应位置再和原位置的查找串进行比较。

突然发现getnext的过程实际上就是一个拿后缀当查找串和模式串进行匹配的过程,如果后缀新增的字符和前缀新增的字符一样,就继续匹配下一个,在kmp函数中没有额外操作,而getnext中需要为next数组记录值,如果不匹配就跳到前缀后缀的部分匹配的位置继续查找

你可能感兴趣的:(笔记总结,数据结构)