开玩笑呢?学习KMP算法能改变自我认知?| 原力计划
wxin55 2024-11-17 16:48 9 浏览 0 评论
作者 | 落阳学编程
责编 | 王晓曼
出品 | CSDN 博客
前言
近日被朋友问到了字符串匹配算法,让我想起了大二上学期在一次校级编程竞赛中我碰到同样的问题时,为自己写出了暴力匹配算法而沾沾自喜的经历。
现在想来,着实有点羞愧,于是埋头去学习了一下 KMP 算法,为了让自己不至于那么快忘记,也希望小伙伴们能从我的理解中收获一点自己的感悟!
文章带有精心雕琢的动画以便理解。
分析
我们首先来分析一下暴力算法,为鲜花的诞生献上绿叶!
以下文中统一将需要被匹配的字符串(长的那段)称为待匹配串,把用来匹配的字符串(短的那段)称为模式串。
暴力匹配算法的思路很简单,就是每一次都首先将待匹配串和模式串的首字母对齐,然后比对是否相同,若相同则继续比对两个串的下一个位置,如果不相同的话就将模式串向右移动一位,然后再重新开始从头匹配,就像下面这样:
从上面的动画我们可以直观的看出来,下面的模式串在匹配失败之后都只会移动一格,傻里傻气的,这就导致它的时间复杂度是M?N M*NM?N,其中M是模式串的长度,N是待匹配串的长度。
对于这个时间复杂度,我不满意!它太傻了,不符合我聪明睿智的气质!
那就来分析一下为何它这么傻。我们可以看到,在第一次匹配失败的时候,我们肯定希望它向右移动至少两格,因为模式串的第一格和第三格都为a,既然第三格已经匹配成功了,那么把第一格对上第三格匹配的位置,那么无疑肯定也是可以成功的,我们的算法本该知道并且利用这一点的!但是它没有,它太傻了。
嗯,这么一说,好像是感觉应该是要把它向着动态规划的方向改(即利用已有信息为下一步提供便利)。
PS:字符串问题百分之八十以上都可以使用动态规划思想达到较低的时间复杂度。
核心问题
我们大都听过一句老话:人啊,贵在有自知之明。
同时我们肯定也听别人说过:人只有深刻的认识了自己,才能找对位置,迅速地向目标前进!
这两句话用在KMP算法中再合适不过了!
KMP算法的核心便在于,模式串对自己的自我认知!
想一想,我们人对自己的认知是如何的:男,19岁,阳光帅气聪明机智,这些自我认知都存放在我的脑袋里面。
那么,模式串对自己的认知应该存放在哪呢?
对,就是next数组里面!字符串没有大脑,所以它需要额外的空间来存储它对自己的认知并借此作出高效准确的判断。
那么字符串对自己的认知是怎样的呢?其实很容易理解,就是知道自己身上哪些地方是相同的,这样的话在匹配失败之后就能迅速找准下次开始的点。这里是不是有点模糊了?图来!
以上就是KMP算法的动画,如果觉得动画稍微有点快的话可以多观看几次,在这个动画里我还没有放出next数组的部分,只是用拟人化的手法展现出来。希望大家能够理解,为什么第一次匹配失败可以直接移动两格。
是因为模式串中第三格的a,它知道在第一格有与自己相同的字符,并且把这个信息告诉下一格的字符,让它在匹配失败之后直接把第一格的a移动到它的那个位置上去。
来看看模式串与其对应的next自我认识数组吧。
不要去在意next数组的第一个为什么是-1,这是为了代码写的方便,暂且就给它当成0。
在动画中,当一个字符发出“直接移动”的语句的时候,其实是告诉后一个字符,如果你匹配失败了的话,就直接移动,同时后一个字符对应的next数组值为0,当后一个字符匹配失败了,就移动模式串的长度-这个匹配失败的字符对应的next值个长度。
从第四个字符(i=3)起,它们都在不断告诉后面一个字符:“将i=0移动到i=3的位置”,这句话对于i=4的字符来说,是移动4-1格,对于i=5的字符来说,是移动5-2格,对于i=6的字符来说,是移动6-3格:后面那个减数恰好就是这个字符对应的next数组的值!
因为模式串足够了解自己,所以它能够在匹配失败的时候不用回退,不用每次只移动一格,而是跟随着待匹配串一起移动。待匹配字符串的指针从未回退过,以线性的速度向前一步步越进。
最终:KMP算法的时间复杂度是M+N M+NM+N
这里我们不禁发出了感叹!原来认识自己真的这么重要啊!
接下来是求出给定模式串的next数组:
Python3代码奉上:
def get_next_lst(ss: str) -> list:
length = len(ss)
next_lst = [0 for _ in range(length)]
next_lst[0] = -1
i = 0
j = -1
while i < length - 1:
if j == -1 or ss[i] == ss[j]:
i += 1
j += 1
next_lst[i] = j
else:
j = next_lst[j]
return next_lst
这段代码最难理解的就是j=next_lst[j]这句话,其实这句话也是动态规划的一个思想,看我为你剖析一下。
已知蓝色区域相等且长度都为len,那么很明显,next[i] == len,若此时模式串pattern[i] != pattern[j](两个灰色区域不相等)。那么看下图:
若此时next[j] == len(粉色部分)那么S1==S2,又因为next[i] == next[j],所以S1==S3 且 S3 == S4,则可以推出S1 == S4,这样我们就利用前面所获得的信息,推出了S1 == S4这个信息,然后将J移动到S1后一格,只要再次比较patter[i] 与 patter[j]的相等情况,就可以得出next[i+1]的值。这里因为i始终向后移动,所以也是线性时间复杂度的算法。
ohhhhhhhhh~
到这里,大家就明白了为啥KMP算法的时间复杂度是M+N M+NM+N了。
KMP匹配字符串的完整代码附上!
class KMP:
def __init__(self, ss: str)-> list:
self.length = len(ss)
self.next_lst = [0 for _ in range(self.length)]
self.next_lst[0] = -1
i = 0
j = -1
while i < self.length - 1:
if j == -1 or ss[i] == ss[j]:
i += 1
j += 1
self.next_lst[i] = j
else:
j = self.next_lst[j]
self.pattern = ss
def match_str(self, ss:str):
ans_lst =
j = 0
for i in range(len(ss)):
if ss[i] != self.pattern[j]:
j = self.next_lst[j] if self.next_lst[j] != -1 else 0
if ss[i] == self.pattern[j]:
j += 1
if j == self.length:
return i + 1 - self.length
return -1
tmp_kmp = KMP('iabc')
print(tmp_kmp.match_str('adosjfoiajsoifjasiofjoiasdjoiabc'))
版权声明:本文为CSDN博主「落阳学编程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/luoyangIT/java/article/details/106041160
相关推荐
- ES6中 Promise的使用场景?(es6promise用法例子)
-
一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...
- JavaScript 对 Promise 并发的处理方法
-
Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...
- Promise的九大方法(promise的实例方法)
-
1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...
- 360前端一面~面试题解析(360前端开发面试题)
-
1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...
- 前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?
-
Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...
- 最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式
-
看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...
- 前端分享-Promise可以中途取消啦(promise可以取消吗)
-
传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...
- 手写 Promise(手写输入法 中文)
-
前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...
- 什么是 Promise.allSettled()!新手老手都要会?
-
Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...
- 前端面试-关于Promise解析与高频面试题示范
-
Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...
- 宇宙厂:为什么前端离不开 Promise.withResolvers() ?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...
- Promise 新增了一个超实用的 API!
-
在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...
- 一次搞懂 Promise 异步处理(promise 异步顺序执行)
-
PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...
- Promise 核心机制详解(promise机制的实现原理)
-
一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...
- javascript——Promise(js实现promise)
-
1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- ES6中 Promise的使用场景?(es6promise用法例子)
- JavaScript 对 Promise 并发的处理方法
- Promise的九大方法(promise的实例方法)
- 360前端一面~面试题解析(360前端开发面试题)
- 前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?
- 最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式
- 前端分享-Promise可以中途取消啦(promise可以取消吗)
- 手写 Promise(手写输入法 中文)
- 什么是 Promise.allSettled()!新手老手都要会?
- 前端面试-关于Promise解析与高频面试题示范
- 标签列表
-
- hive行转列函数 (63)
- sourcemap文件是什么 (54)
- display none 隐藏后怎么显示 (56)
- 共享锁和排他锁的区别 (51)
- httpservletrequest 获取参数 (64)
- jstl包 (64)
- qsharedmemory (50)
- watch computed (53)
- java中switch (68)
- date.now (55)
- git-bash (56)
- 盒子垂直居中 (68)
- npm是什么命令 (62)
- python中+=代表什么 (70)
- fsimage (51)
- nginx break (61)
- mysql分区表的优缺点 (53)
- centos7切换到图形界面 (55)
- 前端深拷贝 (62)
- kmp模式匹配算法 (57)
- jsjson字符串转json对象 (53)
- jdbc connection (61)
- javascript字符串转换为数字 (54)
- mybatis 使用 (73)
- 安装mysql数据库 (55)