KMP算法

Posted 4nc414g0n

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法相关的知识,希望对你有一定的参考价值。


1)概念,专用名词


KMP算法简介

KMP算法是三位学者D.E.Knuth,J.H.Morris和V.R.Pratt在 BF算法(暴力枚举)的基础上同时提出的模式匹配的改进算法。BF算法在模式串中有多个字符和主串中的若干个连续字符比较都相等,但最后一个字符比较不相等时,主串的比较位置需要回退。KMP算法在上述情况下,主串位置不需要回退,从而可以大大提高效率
.
在重叠性较多的字符串中高效率更为明显
.
KMP算法的时间复杂度O(m+n) 而BF算法最坏时为O(mn)


字符串模式匹配

查找模式串在目标串中的位置,例如:

  1. 目标串:“abcdefabfecd”
  2. 模式串:“def”,“abcd” … …

真前缀,真后缀
前缀函数详细介绍参见前缀函数

一个串除该串自身外的其他前缀和后缀
例如:

  1. abc的真前缀为a,ab
  2. abc的真后缀为c,bc

2)KMP和BF(暴力)的思路及比较


BF算法代码

int IndexBF(char* chang,char* duan)//chang为目标串,duan为模式串
{
	int c=0,d=0;
	int c_len=(int)strlen(chang);
	int d_len=(int)strlen(duan);
	while(c<c_len && d<d_len)
	{
		if(chang[c]==duan[d])
		{
			c++;
			d++;
		}
		else{
			c=c-(d-1);
			d=0;
		}
	}
	return d==strlen(duan) ? (c-d) : -1;
}

分析:较为常规,挨个比较即可,思路图如下
在这里插入图片描述


KMP算法代码 (按照思路的大体走向 有一些问题)
注释部分为区别

int IndexKMP(char* chang,char* duan)
{
	//int*next=genNext(duan);
	int c=0,d=0;
	int c_len=(int)strlen(chang);
	int d_len=(int)strlen(duan);
	while(c<c_len && d<d_len)
	{
		if(chang[c]==duan[d])
		{
			c++;
			d++;
		}
		else{
			//d=next[d];
		}
	}
	return d==strlen(duan) ? (c-d) : -1;
}

KMP算法大体思路

先上图在这里插入图片描述

.
分析: 图中是未匹配成功的案例,包含两种情况:匹配失败时字符前面字符串的最长真前缀和真后缀相同的个数 为0 和 不为0

  1. 第一次:判断到b时,与目标串不匹配,并且知道前面的#和$不是@
  2. 第二次:所以从匹配失败处开始继续匹配,第一个就匹配失败
  3. 第三次:继续后一个匹配,直到失败
  4. 第四次:同第二次
  5. 第五次:同第三次
  6. 第六次:匹配到失败处#发现有相同的真前缀和真后缀
    (如果像第二种情况直接从匹配失败处继续匹配会跳过一种可能匹配成功的情况)
  7. 第七次:此时由于前两个@ #一定与目标串匹配,所以我们从模式串的下标2处也就是$开始与目标串匹配(下标正好是最长真前缀和真后缀相同的个数)

结论

  1. 明显效率比BF算法高
  2. 目标串在比较时下标一直没有回退
  3. 需要一个数组来专门储存模式串每个字符之前字符串的最长真前缀和真后缀相同的个数,便于直接使用

3)next数组(存放真前缀和真后缀相同的个数)


int IndexKMP(char* chang,char* duan)
{
	int c=0,d=0;
	int c_len=(int)strlen(chang);
	int d_len=(int)strlen(duan);
	int next[100]={0};//创建duan模式串的next数组,此处可用calloc动态开辟数组空间
	getNext(duan,next);//创建duan模式串的next数组
	while(c<c_len && d<d_len)
	{
		if(chang[c]==duan[d])
		{
			c++;
			d++;
		}
		else{
			d=next[d];//模式串向后跳next[d]再匹配
		}
	}
	return d==strlen(duan) ? (c-d) : -1;
}

当为如上代码且next数组为下图时
.在这里插入图片描述
结合代码分析(特殊情况):

  1. 当模式串第一个字符就和目标不相同时,进入else语句,即d=next[d];d被重新赋为0,而d在进入while循环前就为0,因此while循环进行的条件判断一直成立会造成死循环

解决方法

  1. 在原代码if条件语句之前再加上一个if条件语句
    if(d==0 && chang[c]!=duan[d]) c++;
  2. 更简洁的方法是 将next数组下标为0的元素改为-1如下图,同时将原有的if条件改为
    if(d==-1 || chang[c]==duan[d])
    当d为-1时进入if语句,目标串chang下标c++,模式串duan下标d++,由-1变为0
    在这里插入图片描述

next数组算法(代码实现)

void getNext(char* duan,char* next)
{
	
	int c=0,d=-1;
	next[0]=-1;
	int c_len=(int)strlen(duan);
	while(c<c_len-1)
	{
		if(d==-1 || duan[c]==duan[d])
		{
			c++;
			d++;
			next[c]=d;
		}
		else{
			d=next[d];
		}
	}
}

看图 此图已经很详细了
在这里插入图片描述
分析
思路类似于KMP算法主思路
注意点:

  1. d=-1的赋值非常重要(理解了就会发现此代码非常简洁厉害)
  2. 最后一次不用匹配

以上是关于KMP算法的主要内容,如果未能解决你的问题,请参考以下文章

数据结构—串KMP模式匹配算法

Python ---- KMP(博文推荐+代码)

KMP算法及Python代码

KMP算法及Python代码

图解KMP算法原理及其代码分析

Kmp算法Java代码实现