一分钟教你弄懂KMP算法

问题背景

KMP算法主要应用与字符串的比较,有一个主串,有一个子串,我们要通过一种方式来查看子串是否为主串的一部分。我们通常的想法是:主串和子串左对齐,一个字符一个字符进行比较,如果其中有个字符不匹配的话,那么主串不同,子串向右移动一个范围。这样不断循环,直到找到一个完整的匹配的子串。但如果这样做的话,无疑时间复杂度是最高的,时间复杂度是mn。那有没有更快的方式呢?KMP算法的特点就是一个快速,能够在这种情况下快速地找到一个匹配的子串。

KMP算法是什么呢?

KMP算法操作呢?这个时候就要引入一个公共前后缀和最大公共前后缀的概念,比如这样的一串字符“A B A B A B” 这样的一串字符有很多公共的前后缀,AB是一个ABAB也是一个,甚至没有限制的话ABABAB也能算一个。那最大公共前后缀呢?这里我们的最大公共前后缀实际上有一个限制,那就是小于自身长度的最大公共前后缀,这个时候的最大公共前后缀就是ABAB了。知道这个有什么用呢?我们知道在傻瓜式的比较方式中,每次比较完了之后,子串不仅要向右移动,其中代表子串数组下标用于移动的变量,我们暂且称之为指针(虽然不是真正意义上的),这个指针是需要回溯的,也就是回到子串的首部进行重新比较。这个时候我们就有一个让指针保持在原地的方式,那就是让子串移动到的公共前缀和公共后缀重叠的地方来,然后在指针的位置进行比较。那有些人会有疑惑:那你这样移动那么多位数的话,那中间是不是错过了匹配的情况呢?事实上是不会的,如果错过了,那一定是公共前后缀选择小了,即找的那个并不是真正的公共前后缀这个点就是这个KMP算法的核心,也就是这个算法的神奇之处。如果不太理解,记住即可,这个就是事实。

next数组

很好,我们有了基本的操作方式,可以高效率地判断这个子串是否是这个主串中的一部分了。如果我们止步于此,那么我们就只能手工地进行一些简单的判断,但是进入到代码层面,要考虑的事情还有很多。

首先我们面对的是各式各样的比较,在我们拿到一个主串和一个子串的时候,我们是不能预先知道这个子串在和主串比较的时候在那个元素上发生故障的,所以我们在操作的时候,每次都得计算最大公共前后缀是那个,然后才能移动,那有没有可能将这个过程封装起来,类似于一个函数,我们首先假设每一个位置都会失败(因为运行之前我们不知道哪个会失败,所以只能假设全都失败),然后进而求出他们的公共前后缀(公共前后缀只需要知道子串就好了),然后再计算他们移动的位数,这样的话,在真正的执行的过程中,那个位置比较失败了,就调用相关的函数将其中的移动的步数求出来,但是落实到代码当中,内存是不是真正移动的,所以只能是指针移动到子串的那个位置和此时出错的主串元素进行比较,这样话就转化成,某个位置失败了,我们就直接可以知道接下来那个子串的位置和我这个主串的位置进行比较了。那我们还可不可以将这个方式再优化一下,我们能不能提前执行这个函数,将这个位置结果存在一个固定的地方,下次直接调用就好了?是的,这个就是next数组!表示的就是,当某个位置失败的时候,那我们可以通过这个位置的下标访问这个数组,取到这个数组里面的值,这个值就是我们子串中下次和主串相比较的位置下标。

其中的next数组下表是从1开始的,下表0里面是不存东西的。next数组里面的值0表示的就是子串的第一个和主串失败位置的下一个进行比较。 

next数组的代码实现细节

既然有了实现next数组的思路,那具体的代码怎么实现呢?实现的时候要注意什么呢?如果我们以遍历的方式来求next数组的话,那么具体的过程是这样的:对于每一个子串失败的位置,我们都需要用两个指针在失败位置前的全部部分,一左一右进行扫描,跳出循环的条件是右边到达了最左边,左边到达了最右边,同时两个指针指向的内容进行比较,如果相同的话,继续走,同时计算个数c,如果不对的话跳出循环,这个时候第 c +1个子串的上的元素就要和主串上的元素进行比较,这样的下来的话,要走n趟,每一趟的复杂度也是n,总的来说的是n^2,这样的话就和不采用next数组的傻瓜方式的时间复杂度就是一样的了。那能不能优化呢?

我们在从子串的第一个元素到最后一个元素都在计算Next数组的值,值也是算一个放一个,那算后续Next的值的时候,能不能使用之前已经计算过的Next的值呢?答案是可以的。我们观察上一个部分的next数组的部分的时候,就发现了这样的一个规律:我们next数组中存的其实就是最大公共前后缀 + 1 后的数值,这个很重要!比如我们计算了第n个位置的next数组的值之后,想要计算n + 1个位置上的Next数组上的值,怎么计算呢?这个时候就得看最大公共前后缀!如果我们发现,子串中n上的值和n的失败过后找到的位置t上面的值是相同的话,那么这就意味着,子串上的第 n+ 1 个元素的最大公共前缀是比n的多一个的,这不就意味着,n + 1 在next数组中的值是 n在next数组中的值 + 1吗;如果不相等的话,这就有趣了,看下图!

如果不相等的话,这不就回到我们问题的初始阶段吗,可以将上述的子串看做是主串,下面的子串就是比较的子串。因为我们现在求得是最大公共前后缀,只需要子串就好了,不需要主串,因为我们求最大公共前后缀的前提就是假设某一个元素的失败的,已经过了和主串比较的阶段,所以这里只讨论子串。这个时候Pt和Pj(这里的j就是上述说的n)比较失败,那我们就得寻找子串 t 位置上的公共前后缀,我们有吗?肯定是有的,我们都讨论到了子串j上的Next数组的值,那小于j的t上的值是一定有的,这个时候直接查next数组就好了,然后再将这个值赋值和t,也就是t = next[t],有些人看这个代码有点不明白,是的,初看的时候确实有点模糊,心里想为什么不换一个变量名字区分一下呢?其实这样做也是为了重复利用变量t这部分的空间,如果还看不明白,就只要记住赋值号的结合性是从右向左的,而不是从左向右的,这就是为什么很疑惑的原因了,只要搞清楚了语法的具体执行步骤,先取出t所在next数组里面的值,再赋值给t,覆盖掉之前在t中的值,这个时候t就是一个新的t了,这样就不会疑惑了。这样不断寻找的话,只要找到了新的t中的值是和原来的j所在的值是相同的,那么 子串中j + 1所在位置失败的时候,寻找的位置就是 (新 t + 1),如果到最后还是没有找到的话,也就是说找到了子串的第一个进行比较还是失败的话,注意这里很容易被绕进去,现在我们讨论的是公共前后缀的个数,我们现在是计算 j +1的公共前后缀的个数(不要和正式的主串和子串的比较产生混淆),现在正在用j + 1前面 j 的值和子串中第一个值进行比较,只有第一个值失败之后,我们再回到next数组里面的才会有 t = 0,当出现t = 0的时候,这就意味着连第一个位置都不和 Pj是相同的,那么这就代表着   j + 1前面的所有部分是没有公共前后缀的,即为0,所以当 第j +1 个位置失败之后,是需要第一个元素来个主串比较的,不管是next数组的规律公式层面,还是理解层面,这个时候 j + 1在next数组里面需要填写的值是 0 + 1 或者是1. 所以才会出现next[j + 1] = 1; 

值得注意的是,在整个next数组中,只有子串中第一个元素失败的时候,其中的next值才是0,其代表的意思是:子串的第一个元素和主串第n个位置比较失败,这个时候,子串中已经没有可以比较的元素了,所以主串和子串的匹配开头元素一定不是从这个元素开始的,所以接下来的策略是将子串想后移动一位,让子串的第一个元素和主串的第n + 1个元素进行比较。

有了next数组就够了吗?还可以优化吗?不太够,可以优化。如果我们出现这样的情况:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/255584.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

生成器简述 - python 基础进阶知识点补全(一)

可迭代对象: 可以用于for ... in ..循环对对象都是可迭代对象,比如: list tuple dict set 可以迭代的对象就是可迭代对象,python 中一切都是对象,在这里主要说的是变量 a [1,2,3] b (1,2,3,) c "1234&q…

GPT开发实战:解决GPT API限速问题

一个健壮的、安全的开放平台的架构设计,必然会针对对外开放的API接口进行速率限制,来保证整体系统的可用性,OpenAI对外的API也不例外,我们可以简单的从官方发现API使用量的限制。【API Doc上的限制】【个人账户里的速率限定以及当…

KubeSphere Marketpalce 上新!Databend Playground 助力快速启动数据分析环境

12 月 5 日,Databend Labs 旗下 Databend Playground(社区尝鲜版)成功上架青云科技旗下 KubeSphere Marketplace 云原生应用扩展市场,为用户提供一个快速学习和验证 Databend 解决方案的实验环境。 关于 Databend Playground Dat…

全球市场调研:找准热门产品,开创跨境电商新蓝海

在全球数字化浪潮的推动下,跨境电商正蓬勃发展,成为连接世界各地消费者与商品的桥梁。然而,在竞争激烈的市场中要想脱颖而出,关键在于深入的全球市场调研。本文将探讨如何通过全球市场调研找准热门产品,开创跨境电商的…

如何使用phpStudy本地快速搭建网站并内网穿透远程访问

文章目录 使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点,测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中,查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2.2 映…

使用Rust 构建C 组件

协议解析,这不就很快了,而且原生的标准库红黑树和avl 树支持,异步tokio 这些库,编写应用组件就很快了 rust 标准库不支持 unix 的消息队列,但是支持 shm 和 uds,后者从多方面考虑都比,消息队列更…

STM32Cube高效开发教程<基础篇>(十一)----数据的“高速公路”:DMA(直接存储器访问)

声明:本人水平有限,博客可能存在部分错误的地方,请广大读者谅解并向本人反馈错误。    本专栏博客参考《STM32Cube高效开发教程(基础篇)》,有意向的读者可以购买正版书籍辅助学习,本书籍由王维波老师、鄢志丹老师、王钊老师倾力打造,书籍内容干货满满。 一、DMA功能概…

什么是SPA(Single Page Application)?它的优点和缺点是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【项目】学生信息管理系统

概述 本系统总耗时 6 6 6 天,包括 学生发展与数据驱动平台6.2.cpp、学生信息.txt、用户账号.txt、注意事项.txt。由于代码对文件的调用使用的是相对路径,所以要求这 4 4 4 个文件都需要在同一目录。使用代码前先仔细看 注意事项。 如图: …

Unity传送门特效: The Beautiful Portal/Level up/Teleport/Warp VFX

7种不同风格的传送门特效! 每个传送门都有一个轻型和重型版本。 每个版本都有一个"无循环”和一个"无限”预制件:D 总共有28个预制件 -VFX完全使用Unity的粒子系统和基本的Unity着色器。 使用标准渲染管道中制作了这个资产。所以VFX的功能就像视频宣传片一样。 同时,…

用perl解决小朋友问的2的10000次方是多少的问题

2的10000次方是多少,用perl单行命令搞定, perl -Mbigint -le print 2**10000如果是安装了strawberry perl ,在Windows控制台上输入,单行命令的单引号要换成双引号。 perl -Mbigint -le "print 2**10000"在git-bash中执…

如何处理3dmax渲染完成后阴影部分?

使用3dmax软件,对效果图进行渲染过程中,有不少小伙伴,在渲染完成后出现问题。 较为常见的3dmax渲染问题有3dmax渲染有阴影? 对于一些新手伙伴遇到这类问题,不知如何解决,就会苦恼3dmax渲染有阴影怎么办&am…