KMP算法图解易懂版(内含模板代码)

先了解一下kmp的原理吧

没有耐心看文字的也可直接移步b站哦~

手算版:「天勤公开课」KMP算法易懂版_哔哩哔哩_bilibili

 代码版:KMP算法之求next数组代码讲解_哔哩哔哩_bilibili

目录

(一)什么是kmp?

(二)几个概念

(三)推理核心步骤

(四)手算next数组

(五)代码模板

 1.next数组

例子很好理解!

2.kmp模板代码


(一)什么是kmp?

在主串中快速找到所需子串的起始位置。时间复杂度O(m+n)。比暴力好太多...

解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。

(二)几个概念

公共子串:从左边开始模式串与主串相等部分的串

公共前后缀:公共子串中左右两端相同的字符串,前者为前缀,后者为后缀,注意是在比较指针以左,如果有多对公共前后缀,就取最长的,但不能超过公共子串

 上图所示AB是最长公共前后缀,下面看一个更长的ABA是最长公共前后缀,前后缀可以交叉但不能完全重叠。

(三)推理核心步骤

  • 将模式串后移,使得前缀移到后缀所在位置。
  •  比较指针再次移动,再次移到不等的位置
  •  再次找最长公共前后缀

 (1)为什么公共前后缀必须在公共子串的两端,AB为什么不行?

 因为当模式串右移时,主串的A与模式串的B不对应,不能满足要求。因此AB不是公共前后缀

(2)为什么公共前后缀要找最长的?

 按移动的距离来看,最长子串移动距离更短,如果移动后的位置就是最终结果,那么很显然,第一个才是所需子串第一次出现的位置。

  • 继续右移,直到找到对应下标或模式串尾部超出主串尾部。

(四)手算next数组

但是在计算机中,串并不能移动,接下来就用计算机更能理解的方式进行处理,也就是指针(或者下标)的变化这里idx1指主串的比较指针,idx2指模式串中比较指针所在位置,length指最长公共前后缀长度。

先把规律告诉大家:idx2  =  length  +  1

 接下来进行推导,算法的本质其实与主串关系不大,因此我们需要对模式串进行处理分析。(这里从1开始计数)。假设初始值idx1=idx2=1;

以这个数组为例:

  1. 如果模式串的1号位与主串当前位不匹配,就让模式串1号位与主串下一位比较
  2. 如果模式串的2号位与主串当前位不匹配(前面的都匹配,从第二步开始都是,后面不再赘述),此时最长公共前后缀长度为0,就让1号位与主串当前位比较,相当于模式串向后移了一位。
  3. 如果模式串的3号位与主串当前位不匹配,此时最长公共前后缀长度为0,就让1号位与主串当前位比较,处理方式与2一致。
  4. 如果模式串的4号位与主串当前位不匹配,此时最长公共前后缀为A长度为1,就让2号位与主串当前位比较,相当于1号位后移到3号位,第4号位正好是原来2号位的位置.
  5.  如果模式串的5号位与主串当前位不匹配,此时最长公共前后缀为AB长度为2,就让3号位与主串当前位比较,相当于1号位后移到3号位,第5号位正好是原来3号位的位置.
  6. 如果模式串的6(idx1=6)号位与主串当前位不匹配,此时最长公共前后缀为ABA长度为3(length=3),就让4(idx2=length+1)号位与主串当前位比较,相当于1号位后移到3号位,第6号位正好是原来4号位的位置.经过推理得出模式串中比较指针所在位置=最长公共前后缀长度+1(当起始值为0时也可以表示为前缀最后一位的下标)
  7. 继续验证一下:如果模式串的7(idx1=7)号位与主串当前位不匹配,此时最长公共前后缀为A长度为1(length=1),就让2(idx2=length+1)号位与主串当前位比较,相当于1号位后移到3号位,第7号位正好是原来2号位的位置.
  8. 。。。。。如上推导得出最终结果
  9. 可以看到除了第一列,其他列除了数字以外都相同,因此我们把第一列设置为方法0,其他列结合上面的数组下标,按数字i标记为方法i存到数组中。

这就是next数组。 

有一些规律
1、next[j]的值 每次最多增加1
2、模式串的最后-位字符不影响next数组的结果


(五)代码模板

没有耐心看完的直接移步b站吧!up讲得很好KMP算法之求next数组代码讲解_哔哩哔哩_bilibili

接下来开始盘代码:虽然手算很容易 但是代码看不懂啊,呜呜呜我看了第四遍才看懂的这个代码。。

 1.next数组

  • 初始化next[1]=0;i为比较指针下标,j为最长公共前后缀长度,ch,p为模式串,length为模式串长度
  • while递归求出p[1]~p[i]最长公共前后缀长度
  • 如果p[i]与p[j+1]相等,那么最大公共前后缀长度加1,不等的话回溯推导。
int GetNext(char ch[],int length){
    next[1]=0;
    int i = 1, j = 0;
    while(i<=length){
        if(j==0||ch[i]==ch[j])next[++i]=++j;
        else j=next[j];
    }
}

 不理解没关系,跟着我推导一遍!或者直接看下面的例子

 发现一些规律:

1.首先要看ch[i]和ch[j]是否相等,不相等的话,j=next[j],也就是ch[i]会跟ch[next[j]]进行比较,以此类推。

2.next[j+1]的最大值是next[j]+1

3.如果p[j]!=p[next[j]],next[j+1]可能的次大值为next[next[j]]+1。(这句话可太难懂了)

 既然原理这么难懂,就举一个例子吧

坚持一下!

例子很好理解!

 我们假设i+1=17,那next[16]即已知,假设是8,那么p[1-7]=p[9-15](由前后缀的性质)。

 如果p8=p16,明显next[17]=8+1=9( 对应next[++i]=++j; ),因为前后缀长度加一。如果p8!=p16(此时i=16,j=8,对应ch[i]!=ch[j]),假设next[8]=4( j=next[j]=next[8]=4 ),有以下关系。橙色线框部分字符串完全相同,由前后缀定义和对称性可得。

由此可得,上图中的1和4 相等,如果p16=p4,那么p[1-4]=p[13-16],则next[17]=5

 以次类推,若p16!=p4,next[4]=2,则有以下关系:

 若p16=p2,则next[17]=2=1=3,否则继续取next[2]=1,next[1]=0.遇到0时还没有结果(对应j==0),next[17]=1 (next[++i]=next[17]=++j=1).

 这下差不多明白了。

2.kmp模板代码

和next数组大同小异,一起看看注释吧   ​​​​​​

//KMP算法,判断pattern是否是text的子串
bool KMP(char text[],char pattern[])
{
	int n=strlen(text),m=strlen(pattern);   //求字符串长度
	getNext(pattern,m);     //计算pattern的next数组
    int i = 1, j = 0;
    while(i<=n){
        if(j==0||text[i]==pattern[j])
        {
            j++;i++;
            if(j==m)return true;//pattern匹配完毕,返回true
        }
        else j=next[j];//不断回退,直到j回到0或text[i]==pattern[j]
    }
	return false;   //执行完text还没匹配成功,说明pattern不是text的子串
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值