本文将从原理上详细解释KMP算法中的next数组以及nextval数组,尽量让大家明白它们到底在记录什么,为什么要这样算。以及现在普遍的KMP算法实现当中的next数组与前两者有何不同。篇幅较长,但尽量讲清楚。

文章目录

  • next数组
    • next数组到底在记录什么?
  • nextval数组
    • nextval数组在记录什么? 为什么需要nextval数组?
  • 现在普遍的KMP实现算法中的next数组又在记录什么?
    • 相同之处
    • 不同之处

next数组

next数组到底在记录什么?

虽然数据结构中对next数组有定义,但并不易于理解,因此我个人对next数组进行了一个简单解释:

next数组指示了当前模式串在该位置匹配冲突(即失配,个人习惯称冲突)时,应该将模式串的哪一位与此位对齐。

这里为刚学习KMP的朋友解释一下,KMP算法进行字符串匹配时,被检索的串称为“文本串”,要检索的目标串称为“模式串”。next数组与nextval数组的求解只与模式串有关。

举个例子
假设需要在字符串 “abaabbcabaabcac” 中查找是否存在字符串 “abaabcac”,那么 "abaabbcabaabcac"就是文本串,"abaabcac"就是模式串。

这里其它的许多详解中会直接告诉你,next数组的第一位是0,第二位是1,然后从第三位开始继续算什么的,但很少看到有解释为什么第一位一定是0,第二位一定是1。

为了解释这个问题,我们首先要搞清楚模式串的“位”是什么样的。
对“abaabcac”来说,第一个字符a就是第一位,不要受一些编程语言思维的影响认为这是第0位。

位数 1 2 3 4 5 6 7 8
模式串 a b a a b c a c

但是为了便于理解,我们假想模式串的最前面还有一个空白字符位,称为第0位,即:

位数 0 1 2 3 4 5 6 7 8
模式串 a b a a b c a c

现在,我们再来对照前面给出的定义:
next数组指示了当前模式串在该位置匹配冲突时,应该将模式串的哪一位与此位对齐。

是不是就理解为什么第一位一定是0,第二位一定是1了?
还没有?那我们再假想一个文本串加上来。

待求位:      1  2  3  4  5  6  7  8
文本串:      x  x  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:      a  b  a  a  b  c  a  c

现在标红的位置发生冲突,那么下一步应该让模式串的哪一位跟当前冲突的 x 位对齐呢?没错,就是第0位。也就是变成:

待求位:      1  2  3  4  5  6  7  8
文本串:      x  x  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:          a  b  a  a  b  c  a  c
位数:          0  1  2  3  4  5  6  7  8

所以,当模式串的第一位发生冲突时,应该将模式串的第0位与当前位置对齐,也就是next[1]=0;


继续看第二位发生冲突的情况:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  x  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:      a  b  a  a  b  c  a  c

此时需要将模式串移动为:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  x  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:          a  b  a  a  b  c  a  c
位数:          0  1  2  3  4  5  6  7  8

所以,当模式串的第二位发生冲突时,应该将模式串的第1位与当前位置对齐,也就是next[2]=1;

无论模式串如何变化,只要其长度大于等于2,这两种情况都是固定的,因此next[1]一定为0,next[2]一定为1。


继续看第三位发生冲突的情况:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:      a  b  a  a  b  c  a  c

这时,假如我们仍然只移动一位,可以吗?

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:          a  b  a  a  b  c  a  c

可以发现,如果仍然只移动一位,那么 x 位之前的位置也出现了冲突!

因此究竟该将模式串的哪一位与冲突的位置对齐呢?
一个重要原则是:当前冲突位置之前的一位不能有冲突!

换句话说,要解决第三位的冲突,移动模式串时起码要保证第二位是无冲突的。

那现在出现冲突了怎么办呢?



想想next数组本身的作用?不就是指示了出现了冲突时,应该将哪一位与该位置对齐吗?!

想到这里,问题就变得简单了。

现在第二位出现了冲突,按next[2]指示应该将第一位的a与该位置对齐,那现在冲突解决了吗?
显然b不等于a,没有解决。但是问题已经转化成了第一位的位置出现冲突了!
继续查next[1],等于0,也就是说需要将模式串的第0位与该位置对齐。第0位是空的,我们认为它一定不会产生冲突,至此,第二位的冲突顺利解决了,那现在我们和第二位对齐的是哪一位呢?没错,是第0位,如下:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  x  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:              a  b  a  a  b  c  a  c
位数:              0  1  2  3  4  5  6  7  8

那和第三位(待解决的冲突位)对齐的自然是第0位的下一位,即第一位。由此,便可以得到next[3]=1。

而这个过程就是其他计算详解中所提到的,【找到当前要求的next字符的前一个字符,以它为标准,找到其next对应下标的字符,和这个字符做比较。若相等,那么当前字符的next值就位此字符next加一;若不等,继续找现在字符next所指的下一个字符,还是和之前的字符比较,直到找到第一个位置为止,那么next为1。】其实就是依据现有next值解决前一位冲突的过程。


为了让大家更熟悉这个过程,再继续看一下第四位发生冲突的情况:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  a  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:      a  b  a  a  b  c  a  c

按前面所讲,要确定需要让哪一位与第四位对齐,起码要保证第三位是无冲突的。
第三位是什么?字符a,第三位冲突时需要对齐的是哪一位?是next[3]=1,即第一位的字符a。
显然a和a是相等的,至此第三位的冲突已经解决,我们将模式串的第一位与第三位对齐,如下:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  a  x  x  x  x  x  x  x  x  x  x  x  x  x
模式串:              a  b  a  a  b  c  a  c
位数:              0  1  2  3  4  5  6  7  8

那和第四位(待解决的冲突位)对齐的自然是第一位的下一位,即第二位。由此,便可以得到next[4]=2。


第五位冲突的情况:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  a  a  x  x  x  x  x  x  x  x  x  x  x  x
模式串:      a  b  a  a  b  c  a  c

至少保证第四位无冲突,第四位为字符a,next[4]=2,所指示字符为第二位的b,a不等于b,转化为第二位的冲突;next[2]=1,所指示的字符位第一位的a,a等于a,冲突解决。此时和第四位对齐的是第一位的a,如下:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  a  a  x  x  x  x  x  x  x  x  x  x  x  x
模式串:                  a  b  a  a  b  c  a  c
位数:                  0  1  2  3  4  5  6  7  8

那和第五位(待解决的冲突位)对齐的自然是第一位的下一位,即第二位。由此,便可以得到next[5]=2。


第六位冲突的情况:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  a  a  b  x  x  x  x  x  x  x  x  x  x  x
模式串:      a  b  a  a  b  c  a  c

至少保证第五位无冲突,第五位为字符b,next[5]=2,所指示字符为第二位的b,b等于b,冲突解决。此时和第五位对齐的是第二位的b,如下:

待求位:      1  2  3  4  5  6  7  8
文本串:      a  b  a  a  b  x  x  x  x  x  x  x  x  x  x  x
模式串:                  a  b  a  a  b  c  a  c
位数:                  0  1  2  3  4  5  6  7  8

那和第六位(待解决的冲突位)对齐的自然是第二位的下一位,即第三位。由此,便可以得到next[6]=3。


第七位和第八位的冲突大家可以自行计算,最终便可以得到模式串的next数组值:

位数 1 2 3 4 5 6 7 8
模式串 a b a a b c a c
next值 0 1 1 2 2 3 1 2

nextval数组

nextval数组在记录什么? 为什么需要nextval数组?

其实和next数组一样,nextval数组也是用来指示当前模式串在该位置匹配冲突时,应该将模式串的哪一位与此位对齐的。

既然nextval数组的作用和next数组一样,那为什么需要nextval数组呢?
在严蔚敏的《数据结构(C语言版)》中,说明了next数组存在的问题,我们同样以该例子来讲。

假设文本串为 “aaabaaaab”,模式串为 “aaaab”。
根据前面的讲述,我们可以得到模式串的next数组值如下:

位数 1 2 3 4 5
模式串 a a a a b
next值 0 1 2 3 4

冲突发生在第四位:

文本串:      a  a  a  b  a  a  a  a  b
模式串:      a  a  a  a  b

根据next值,next[4]=3,需要将第3位与该位置对齐:

文本串:      a  a  a  b  a  a  a  a  b
模式串:          a  a  a  a  b

但此时仍有冲突,这时冲突发生在模式串的第三位,根据next[3]=2,需要将第二位与此位置对齐:

文本串:      a  a  a  b  a  a  a  a  b
模式串:              a  a  a  a  b

仍有冲突,发生在模式串第二位,next[2]=1,将模式串第一位与此位置对齐:

文本串:      a  a  a  b  a  a  a  a  b
模式串:                  a  a  a  a  b

依然冲突,发生在模式串第一位,next[1]=0,将模式串第0位与此位置对齐:

文本串:      a  a  a  b  a  a  a  a  b
模式串:                      a  a  a  a  b

至此,冲突解决,匹配成功。
但整个过程效率很低,因为模式串前四个字符都为a,当第一次发生冲突时,就可以知道应该直接将第0位和该位置对齐,中间的几次比较和移动都是多余的。
因此,nextval所做的工作就是对next值进行一次修正,排除掉无效移动。

而计算nextval值也有一个原则:保留不同

为什么说是保留不同呢?分析一下上述例子问题产生的原因就清楚了:

由于计算next值时,我们不清楚当前位置的情况,最多只考虑到了冲突的前一位,就可能导致移动后对齐的字符仍和上一个冲突的字符相同,而这种情况必然会继续冲突。

所以我们要对next值进行一次筛选,保证冲突位置指示的应该对齐的字符和原字符不同。(注:这里我们认为第0位的空白字符与所有字符都不同)

以aaaab为例:
next[1]=0,第一位冲突指示的第0位为空白字符,和第一位本身的a不同,保留原next值,故nextval[1]=0;
next[2]=1,第二位冲突指示的第一位为字符a,和第二位本身的a相同,继续看第一位next[1]的值,(因为相同就表示按next值的此次对齐是无效的,会继续发生第一位的冲突,因此继续看next[1])next[1]=0,指示的第0位位空白字符,和第二位本身的a不同,保留next[1]的值,故nextval[2]=0;
以此类推,可以得到nextval值如下:

位数 1 2 3 4 5
模式串 a a a a b
next值 0 1 2 3 4
nextval值 0 0 0 0 4

现在普遍的KMP实现算法中的next数组又在记录什么?

关于这个问题,似乎很少有人进行解释,但这个问题却很容易让人困惑。因为现在普遍的KMP实现算法中所谓的next数组记录的东西和前面所说的next数组以及nextval数组是有区别的

为了进行区分,我们称这些KMP实现算法中的next数组为Knext数组。

虽然Knext数组在使用时的功能和next数组以及nextval数组相似,但是其记录的内容和求解方法却与next以及nextval有着很大不同。

由于nextval与next没有本质区别,我们单比较Knext与next之间的异同。

相同之处

  • Knext和next都是为了提高算法效率,让串的匹配快速找到下一个可能的起始位置,避免文本串指针的回退。
  • Knext和next的求解过程都属于动态规划,需要依赖于之前求解出的Knext值和next值。

不同之处

  • Knext记录的是到对应位为止的最长相等前后缀的长度。

    这一点可以说是Knext与next最本质的区别,next直接指示了冲突发生时需要对齐的模式串位置,而Knext则是提供了一个基于前缀和后缀的参考,若要直接将其作为指示当前位置冲突时需要对齐的模式串位置(该对齐仍然是基于最长相等前后缀进行选择的,但next和nextval不会涉及前后缀的概念),需要做另外的处理(如统一减1或整体右移)。

    这里补充一下什么是最长相等前后缀
    前缀:不包含最后一个字符的所有以第一个字符开头的连续子串
    后缀:不包含第一个字符的所有以最后一个字符结尾的连续子串

    以模式串 aaabbab 为例,
    第一位a:没有前缀和后缀,因此最长相等前后缀长度为0,即Knext[1]=0;
    第二位a:前缀 {a},后缀 {a},最长相等前后缀长度为1,即Knext[2]=1;
    第三位a:前缀 {a,aa},后缀{a,aa},最长相等前后缀长度为2,即Knext[3]=2;
    第四位b:前缀{a,aa,aaa},后缀{b,ab,aab},最长相等前后缀长度为0,即Knext[4]=0;
    第五位b:前缀{a,aa,aaa,aaab},后缀{b,bb,abb,aabb},最长相等前后缀长度为0,即Knext[5]=0;
    第六位a:前缀{a,aa,aaa,aaab,aaabb},后缀{a,ba,bba,abba,aabba},最长相等前后缀长度为1,即Knext[6]=1;
    第七位b:前缀{a,aa,aaa,aaab,aaabb,aaabba},后缀{b,ab,bab,bbab,abbab,aabbab},最长相等前后缀长度为0,即Knext[7]=0。

    为了更直观地看出它们之间的差异,这里给出模式串aaabbab的next、nextval和Knext值:
位数 1 2 3 4 5 6 7
模式串 a a a b b a b
next值 0 1 2 3 1 1 2
nextval值 0 0 0 3 1 0 2
Knext值 0 1 2 0 0 1 0

关于Knext的求解方法这里不再赘述,可以参考代码随想录的相关讲解。

下方代码块是一个典型的Knext数组求解的实现。

void getNext(int* next, const string& s) {int j = 0;next[0] = 0;for(int i = 1; i < s.size(); i++) {while (j > 0 && s[i] != s[j]) { j = next[j - 1]; }if (s[i] == s[j]) {j++;}next[i] = j;}}

根据个人习惯不同,Knext的求解代码可能会在初始化和比较判断时有差异,但其本质是相同的。

【KMP】从原理上详解next数组和nextval数组相关推荐

  1. 最长上升子序列(LIS)/最长不上升子序列问题算法详解+例题(树状数组/二分优化,看不懂你来打我)

    目录 最长上升子序列 一.朴素做法O(2n)O(2^n)O(2n) 二.优化做法O(nlogn)O(nlogn)O(nlogn) 三.例题引入:P1020 导弹拦截(求最长上升子序列和最长不上升子序列 ...

  2. [crypto]-02-非对称加解密RSA原理概念详解

    说明:本文使用的数据来自网络,重复的太多了,也不知道哪篇是原创. 算法原理介绍 step 说明 描述 备注 1 找出质数 P .Q - 2 计算公共模数 N = P * Q - 3 欧拉函数 φ(N) ...

  3. [crypto]-01-对称加解密AES原理概念详解

    1.对称加解密 术语:P是明文,C是密文,K是密钥,E是加密算法,D是解密算 (1).常用的对称加解密有哪些? (2).加解密的模式 [ecb]这种模式是将整个明文分成若干段相同的小段,然后对每一小段 ...

  4. Linux 下 TC 命令原理及详解<一>

    文章目录 1 前言 2 相关概念 3 使用TC 4 创建HTB队列 5 为根队列创建相应的类别 6 为各个类别设置过滤器 7 复杂的实例 Linux 下 TC 命令原理及详解<一> Lin ...

  5. 今日头条推荐算法原理全文详解之一

    本次分享将主要介绍今日头条推荐系统概览以及内容分析.用户标签.评估分析,内容安全等原理. 今日头条推荐算法原理全文详解 今日头条 数据分析 产品经理 产品 好文分享 第1张 一.系统概览 推荐系统,如 ...

  6. 抖音推荐算法原理全文详解

    阅读目录 一.系统概览 二.内容分析 三.用户标签 四.评估分析 五.内容安全 抖音推荐算法原理全文详解 本次分享将主要介绍今日头条推荐系统概览以及内容分析.用户标签.评估分析,内容安全等原理. 回到 ...

  7. 白加黑加载方式_“白加黑减”即曝光补偿的应用方法及原理全面详解——致新手新新手...

    "白加黑减"即曝光补偿的应用方法及原理全面详解 --致新手新新手 本文对曝光补偿这一摄影基本技术及原理做一全面详细的分析和解释,旨在让受此困惑的新手能够从原理上彻底理解" ...

  8. android平台下OpenGL ES 3.0实例详解顶点属性、顶点数组

    OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...

  9. 今日头条推荐算法原理全文详解之四

    三.用户标签 内容分析和用户标签是推荐系统的两大基石.内容分析涉及到机器学习的内容多一些,相比而言,用户标签工程挑战更大. 今日头条推荐算法原理全文详解 今日头条 数据分析 产品经理 产品 好文分享 ...

最新文章

  1. 在Kali上安装打印机
  2. 四叶草社交平台——十天冲刺(5)
  3. srt编辑的文件在linux显示乱码,解决看本地视频srt字幕乱码问题教程-srt文件
  4. java++记录+运行_记录java+testng运行selenium(三)---xml、ini、excel、日志等配置
  5. 百度api 一直提示token错误_phpcms小程序插件小程序万能接口api(支持微信、百度)...
  6. python生成10000个样本数据集_python – 为什么随机抽样比例数据集不是样本量? (pandas .sample()示例)...
  7. 兴趣爱好-常用的10种算法
  8. 浅析DC/DC转换器未来市场的发展前景
  9. 域名邮箱和邮件服务器
  10. 路由与交换技术笔记(eNSP)
  11. 国产ERP有没有机会击败SAP ?
  12. 冲刺大厂每日算法面试题,动态规划21天——第七天
  13. 派对屋3000效果器怎样调试_音响效果器的调试和使用技巧
  14. 修改Endnote插入Word参考文献中位置
  15. Latex 使用\begin{aligned} 出现 Environment aligned undefined.
  16. 迷室3第三章难点问题解读
  17. 【软考|软件设计师】专业英语(软考真题)
  18. #(二)、古人怎么解决波的表达
  19. C#EntityFramework(EF基础)
  20. POE网络变压器的检测要求及具体测试标准

热门文章

  1. IE,chrome,ff的设为首页、加入收藏及保存到桌面js实现
  2. 小米手机设置NFC卡的感应顺序
  3. 政采云基于 sealer 的私有化业务交付实践
  4. 【MySQL学习记录】第四章:运算符及课后练习
  5. poj1185 炮兵阵地
  6. 实用网站推荐,每一个都值得收藏
  7. 猫耳宝贝:“网络打女”身手不凡
  8. Leetcode-链表随机节点
  9. 内存泄漏、内存溢出、内存越界、段错误、堆溢出、栈溢出
  10. ORAN专题系列-2:O-RAN的系统架构