KMP算法

引言

  1. 建议这部分内容不会的同学,可以先弄明白基本概念,这样才能知道书上的定义和定理说的是什么;然后再去理解定义和定理,理解它实现的具体原理,以及为什么要这么做,这么做的利弊;最后再仔细琢磨琢磨实现算法的代码。
  2. 受限于本人能力和讲解形式,有些地方可能说得有欠妥或遗漏之处,故在文末推荐了几个大神制作的教学视频,有不懂的或感兴趣的同学可以看一看。
  3. 码字不易,如果这篇文章对您有帮助的话,希望您能点赞、评论、收藏,投币、转发、关注。您的鼓励就是我前进的动力!

一、基本概念

  1. 前缀:是指除了最后一个字符外,一个串的全部头部组合。 例如在子串「ABCABX」中,A,AB,ABC,ABCA,ABCAB 均是该串的前缀。
  2. 后缀:是指除了第一个字符外,一个字符串全部尾部组合(不包括最后一个字符)。 如上例(ABCABX)中,B,AB,CAB,BCAB 均为该串的后缀。
  3. 部分匹配值:就是前缀和后缀中最长的公共前后缀的长度值。 如上例(ABCABX)中,对于元素X来说,最长前后缀共有的元素为AB两个元素,所以部分匹配值为 2。
  4. next数组值:等于部分匹配值加一(除第一个位置恒等于0外)。 如上例(ABCABX)中,对于元素X来说,部分匹配值为 2,所以 next 数组值为 2+1=3。

二、基本原理

  1. 在暴力匹配算法中,对于在子串中与首字符相等的字符,可以省略一部分不必要的判断步骤(主串的回溯)。而KMP模式匹配算法,就是为了让没必要的回溯不发生(即主串不回溯,子串少回溯)。
  2. KMP算法如何实现主串不回溯,子串少回溯的? KMP算法主要就是在发生主串与子串有元素不匹配时,不移动指针(指针相对于主串静止)只移动子串。并且不是将子串拨回第一个位置,而是拨到恰当位置——将最大公共前后缀里的前缀直接移动到后缀所在位置,由于前后缀是相同的,所以这样移动后,子串与主串在比较指针前面的部分,上下是匹配的。从而实现主串不回溯,子串少回溯。

三、next数组值的推导

  1. 公式:
    n e x t [ j ] { j = 1 时,为 0 j > 1 时,为部分匹配值 + 1 \boxed { next[j] \begin{cases} j=1时,为0\\ j>1时,为部分匹配值+1\\ \end{cases} } next[j]{j=1时,为0j>1时,为部分匹配值+1
  2. 例题:求模式串「abcdabd」的next数组值。
    解析:直接套用公式即可。如下表:
j j j1234567
模式串abcdabd
n e x t [ j ] next[j] next[j]0111123

四、nextval数组

  1. 改进过的KMP算法,它是在计算出next数组值的同时,如果 a 位字符与它的 next 值指向的 b 位字符相等,则该 a 位的nextval就指向 b 位的 nextval 值,如果不等,则该 a 位的nextval值就是他自己 a 位的next 值。即:
    n e x t v a l [ j ] { j = 1 时,为 0 j > 1 时 { j 位字符 = = n e x t [ j ] 位字符,为 n e x t [ j ] 位字符的 n e x t v a l 值 j 位字符 ! = n e x t [ j ] 位字符,为 j 位字符的 n e x t 值 \boxed { nextval[j] \begin{cases} j=1时,为0\\ j>1时 \begin{cases} j位字符==next[j]位字符,为next[j] 位字符的nextval值\\ j位字符 !=next[j]位字符,为j 位字符的next值\\ \end{cases}\\ \end{cases} } nextval[j] j=1时,为0j>1{j位字符==next[j]位字符,为next[j]位字符的nextvalj位字符!=next[j]位字符,为j位字符的next

五、实现代码

  1. 求next数组值函数
void GetNext(String T,int next[])
{
	int i=1, k=0;
	next[1]=0;
	while (i<T[0])   // T[0]为串长
	{
		if (k==0||T[i]==T[k])
		{
			++i; 
			++k;	
			next[i]=k;
		}
		else    
			k=next[k];
	}
}
  1. k是什么? k实际上就是最大公共前后缀,即部分匹配值。代码中语句「++k; next[i]=k; 」就相当于「next数组值=部分匹配值+1」。
  2. 该算法的主要思想就是通过 迭代 的方式,以旧值推导新值。体现如下:
    1)通过指针旧值自增「 i++; 」推出指针的新值,从而实现指针的后移。
    2)求某一元素的部分匹配值,即k的值。
  3. 代码解释(写代码思路)
    1)先定义指针i(注意指针i是从1开始的),部分匹配值k,及next数组。
    2)在循环外,先给第一个位置处的next数组值赋值为0。
    3)接着计算后续next数组值。
    i. 如果部分匹配值为0或(公共)前后缀的最后一个元素值相等(通过循环累加,k值就相当于公共前后缀的长度值),此时的k值就是当前指针所指元素的部分匹配值。自增,赋值给next数组即可。
    ii. 否则(即k值不为0且前后缀的最后一个元素值不相等),说明当前的k值不是对应元素的部分匹配值。将k值回溯到它的next数组值(即k=next[k]; )比较此时前后缀最后一个元素是否相等(T[i]是否等于T[k]; )。若还不等,就再将k值回溯,直至相等或k=0。
    4)如此循环,直至算完该模式串的next数组值,再返回给主调函数。
  1. KMP算法匹配查找函数
int Index_KMP(String S, String T, int pos)
{
	int i=pos;   //主串指针
	int j=1;    //子串指针
	int next[MAXSIZE]; 
	
	GetNext(T, next);   //此处换成GetNextval(T,nextval); 就是改进版的KMP匹配算法
	while ( i<=S[0] && j<=T[0] )
	{
		if ( j==0 || S[i]==T[j] )
		{  
			++i; 
			++j;
		}
		else 
			j=next[j];
	}
	if ( j > T[0] ) 
		return i-T[0];
	else 
		return 0;
}
  1. 代码解释(写代码思路):
    1)先定义主串指针 i,子串指针 j 和next数组,注意主串指针 i 初始化为pos( i 可以不从主串的第一个元素开始匹配),子串指针 j 初始化为1。
    2)调用GetNext函数得到next数组
    3)在指针 i,j 都小于串长时循环。在主串元素等于子串元素时(j=0的情况另讨论),说明元素匹配。将 i,j 均后移,继续比较下一对。否则,说明元素不匹配,主串指针不动,子串指针退回到恰当位置(next值所指的位置),再进行比较。
    4)匹配完后:
    i. 若 j 大于子串串长,说明子串与主串中的元素匹配(此时 j 等于子串串长加一),用主串指针位置减去子串串长,即得子串在主串中的首地址,返回其值
    ii. 否则,说明未在主串中找到与子串匹配的部分,返回0。
  2. 关于if语句中的判断条件中的 j==0 :出现j=0的情况是:子串的第一个元素(next值为0)与主串比较时发生了不匹配,此时j = next[1] = 0; ,而「 ++j; 」就使得指针能再次指向子串第一个元素(不然 j=0 时,指针 j 未指向子串)。此时,由于主串元素与 j=1 的子串元素已经比过了,所以「 ++i; 」让子串的第一个元素与主串的下一个元素比较。
  1. 改进版nextval数组函数
void GetNextval(String T,int nextval[])
{
	int i=1, k=0;
	nextval[1]=0;
	while (i<T[0])   
	{
		if (k==0||T[i]==T[k])
		{
			++i; ++k;	
			
			//比求next数组多的代码
			if (T[i]!=T[k])  
				nextval[i] = k;
			else 
				nextval[i] = nextval[k];
			
		}
		else    
			k=nextval[k];
	}
}
  1. 求nextval数组的代码就是在求next数组的if-else语句中再嵌套一个if-else语句即可求得nextval数组值。
  2. 注意:如果 a 位字符与它的 next 值指向的 b 位字符相等,则该 a 位的nextval就指向 b 位的 nextval 值,不是next值!

六、补充

  1. KMP算法的时间复杂度为O(n+m)。该算法仅当模式串与主串之间存在许多部分匹配的情况下,才体现出它的优势,否则两者(与暴力匹配算法相比)差异并不明显。

七、推荐资料

  1. 青岛大学王卓老师的数据结构与算法课
  2. 天勤考研的KMP算法易懂版
  3. 奇乐编程学院的KMP算法讲解视频

参考资料:
[1] 程杰. 大话数据结构. 北京:清华大学出版社, 2020.
[2]严蔚敏,吴伟民. 数据结构 (C语言版). 北京:清华大学出版社, 1997.
[3]青岛大学-王卓老师数据结构与算法课
[4]天勤考研KMP算法易懂版视频

写在文末:今天是1024程序员节,祝广大程序员及准程序员节日快乐!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉远

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值