KMP算法详解(含视频)

目录

前言

一·前缀、后缀、最长相等前后缀

二·kmp算法逻辑介绍

三·next数组的构建

 四·关于kmp算法的再介绍

总结(内含一些推荐的kmp算法视频)


前言

在我们进行字符串匹配或者说模拟实现C语言库函数strstr()时,我们不难发现暴力求解( BF算法)的时间消耗是非常高的。于是Donald KnuthVaughan PrattJames Morris经研究后于1977年提出了一个更简便的算法,也就是KMP算法。          ------2023.8.18记

在正文开始前,我们需要了解几个概念:前缀与后缀、最长相等前后缀、文本串、模式串


一·前缀、后缀、最长相等前后缀

我们在进行字符串匹配时候,题目会给出两个字符串(可以参考:leetcode上的一道题目28.找出字符串中第一个匹配项的下标),

模式串(Pattern):是指用来进行匹配的字符串,也可以称为待匹配字符串。它是我们需要在一个文本串中查找的目标字符串。

文本串(Text):是指要进行查找或匹配操作的字符串,也可以称为源字符串。它是我们需要在其中进行模式串匹配操作的字符串。

而我们讲的最长相等前后缀是对模式串而言的,因为KMP算法要求我们不回溯文本串

以下图中的两个字符串为例,来解释上面几个概念

前缀(prefix):指的是一个字符串除了最后一个字符外的所有前缀子串(包括空串)

后缀(suffix):指的是一个字符串除了第一个字符外的所有后缀子串(包括空串)。

另外请注意:

对于前缀字符串:其是必须包含字符串首元素的、不包含末尾字符的、连续的、不包括原字符串本身的、所有子字符串

对于后缀字符串:其是必须包含字符串尾元素的、不包含首字符的、连续的、不包括原字符串的、所有子字符串。

 那么对于模式串我们可以分别求出其

前缀:“A”、“AB”、“ABA”、“ABAB”

后缀: "C"、“BC”、"ABC"、“BABC”  

我们不难看出:此时,没有相等的前后缀。

二·kmp算法逻辑介绍

我们先用动图来演示一下

演示视频

(假设我们已经求出了next数组,关于next数组如何构造,我们稍后解释)

我们先对各个变量命个名,以方便解释

文本串text、模式串pattern、文本串长度len_text、模式串长度len_pattern

从中我们不难看出的逻辑是:

1.当 text[i]==pattern[j]  时,   i++,j++

2.当 text[i]!=pattern[j]   时,   j=next[j-1]    

3.当 j==len_pattern       时,   结束查找

注意:因为要防止next数组越界,所以  j-1 >= 0 ,所以  j>0

所以第二条应该改为    j>0&&text[i]!=pattern[j] ,j=next[j-1]

所以我们不妨构建一下这部分代码:

int My_strstr(char * text,char * pattern)
{
   int len_pattern = strlen(pattern);
   int len_text = strlen(text);
   int j,i;
   int *next=Getnext(pattern);
  for(i=0;i<len_text;i++)
  {   
    while(j>0&&text[i]!=pattern[j])
    {
        j=next[j-1];                       //匹配失败移动j
    }
    if(text[i]==patterm[j])
    {         
         j++;                              //单个元素匹配成功
    } 
    if(j==len_pattern)
    {
        return i-len_pattern+1;            //返回第一次成功匹配,text数组对应首元素的下标
    }  
  } 
    return -1;                             //返回-1,意味着字符串不匹配
}

三·next数组的构建

关于next数组的构建实际上就需要用到前文提到的前缀与后缀

我们再来看一个视频:

大黄蜂录屏2023-08-18-15-52-50

从中我们不难发现几条规则:

1.当 pattern[i]==pattern[j] 时,  i++;j++

2.当 pattern[i]!=pattern[j]  时,  j=next[j-1]

3.当 i==len_pattern          时,  检索结束

注意:为了避免指针越界,j - 1>=0 所以 j > 0,所以第二条应该改为

j>0&&pattern[j]==pattern[i] 时,j=next[j-1]

所以我们依此来构建代码:

int *Getnext(char *pattern,int len_pattern)
{
   int i,j;
   int *next=(int *)malloc(sizeof(int)*len_pattern);
   assert(next);
   next[0]=0;                        //我们提前把,next第一个元素置为零,    
                                     //我们在动画中也演示了,第一个元素的前后缀均为空串
                                     //其次,这样做可以让我们复刻 strStr()的逻辑写代码
   for(i=1,j=0;i<len_pattern;i++)    //更加容易记忆
   {
     while(j>0&&pattern[j]!=pattern[i])
      {
         j=next[j-1];                //内容不匹配时,回溯j
      }
      if(pattern[j]==pattern[i])
      {
         j++;                        //更新j,i随着循环自行更新,这里便不再,需要更新i
      }
      next[i]=j;                     //及时更新next数组
   }
   
   return next;                      //返回构建的next数组,一定要是动态开辟的数组
                                     //一方面,next数组大小未知,其次,保证数组在出函 
                                     //数后不被销毁
}

 四·关于kmp算法的再介绍

经过next数组构建后,你可能会心生疑惑:尽管代码我能看懂,但为什么可以这样做?

当你有这个疑问的时候,我便不能再给你解答了,这个时候,你应该去自己画画图,自己敲一敲代码,来熟悉里面的逻辑。(一下的解释便全是基于代码了)

如果你的逻辑还没有很清晰,不妨往下看:

首先你要清楚,构建next数组,包括使用next数组匹配字符串的过程中,“j++”这一条件,不是随着循环进行而不断执行的,他的增加是有条件的,这也其实就造成了,“j”比“i”慢的这种现象,再往深处刨析,j一定是在有对称的部分里存在的,他不可能位于一个非对称区域中这一区域可能是整个字符串,但也可能不是。比如:abcde,没有对称区域,故j只可能在a元素对应的位置处。再比如:abcabcd,有对称区域abcabc,故j只可能存在于前一个abc对应的位置处关于这一点你可以去看看前面的视频演示,当然,这也只是基于代码的一种解释。

而对于i来说,它是一往无前的,是要遍历数组必须要进行的,所以将其设置成随循环递增。

而对于一些其他情况,比如说元素匹配时、不匹配时以及结束条件的判断这里就不多bibi了.


总结(内含一些推荐的KMP算法视频)

KMP算法的学习中,一定要多画图分析,分析结束后,一定要结合自己的分析,进行代码的编写与练习。

给大家推荐几个讲解KMP易懂的b站up主

奇乐编程学院

木子喵neko

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木鱼不是木鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值