近日被朋友问到了字符串匹配算法,让我想起了大二上学期在一次校级编程竞赛中我碰到同样的问题时,为自己写出了暴力匹配算法而沾沾自喜的经历。

现在想来,着实有点羞愧,于是埋头去学习了一下KMP算法,为了让自己不至于那么快忘记,也希望小伙伴们能从我的理解中收获一点自己的感悟!

文章伴有精心雕琢的动画以便理解。

我们首先来分析一下暴力算法,为鲜花的诞生献上绿叶!

以下文中统一将需要被匹配的字符串(长的那段)称为待匹配串 ,把用来匹配的字符串(短的那段)称为模式串

暴力匹配算法的思路很简单,就是每一次都首先将待匹配串和模式串的首字母对齐,然后比对是否相同,若相同则继续比对两个串的下一个位置,如果不相同的话就将模式串向右移动一位,然后再重新开始从头匹配,就像下面这样️️



从上面的动画我们可以直观的看出来,下面的模式串在匹配失败之后都只会移动一格,傻里傻气的,这就导致它的时间复杂度是$M*N$,其中M是模式串的长度,N是待匹配串的长度。

对于这个时间复杂度,我不满意!它太傻了,不符合我聪明睿智的气质!

那就来分析一下为何它这么傻。我们可以看到,在第一次匹配失败的时候,我们肯定希望它向右移动至少两格,因为模式串的第一格和第三格都为a,既然第三格已经匹配成功了,那么把第一格对上第三格匹配的位置,那么无疑肯定也是可以成功的,我们的算法本该知道并且利用这一点的!但是它没有,它太傻了。

嗯,这么一说,好像是感觉应该是要把它向着动态规划的方向改(即利用已有信息为下一步提供便利)。

PS:字符串问题百分之八十以上都可以使用动态规划思想达到较低的时间复杂度。

我们大都听过一句老话:人啊,贵在有自知之明。

同时我们肯定也听别人说过:人只有深刻的认识了自己,才能找对位置,迅速地向目标前进!

这两句话用在KMP算法中再合适不过了!

KMP算法的核心便在于,模式串对自己的自我认知!

想一想,我们人对自己的认知是如何的:男,19岁,阳光帅气聪明机智,这些自我认知都存放在我的脑袋里面。

那么,模式串对自己的认知应该存放在哪呢?

对,就是next数组里面!字符串没有大脑,所以它需要额外的空间来存储它对自己的认知并籍此作出高效准确的判断。

那么字符串对自己的认知是怎样的呢?其实很容易理解,就是知道自己身上哪些地方是相同的,这样的话在匹配失败之后就能迅速找准下次开始的点。这里是不是有点模糊了?图来!



以上就是KMP算法的动画,如果觉得动画稍微有点快的话可以多观看几次,在这个动画里我还没有放出next数组的部分,只是用拟人化的手法展现出来。希望大家能够理解,为什么第一次匹配失败可以直接移动两格。

是因为模式串中第三格的a,它知道在第一格有与自己相同的字符,并且把这个信息告诉下一格的字符,让它在匹配失败之后直接把第一格的a移动到它的那个位置上去。

我这里为了大家容易理解,只放出了一个字符相同的情况,大家不妨可以扩展想一下,假如,第一格和第三格的a不是一个字符,而是一个字符串呢?怎么?有点打脑壳?图来!

来看看模式串与其对应的next自我认识数组吧。

i 0 1 2 3 4 5 6
next -1 0 0 0 1 2 3
string a b c a b c d

不要去在意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$

这里我们不禁发出了感叹!原来认识自己真的这么重要啊!

接下来是求出给定模式串的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$了。

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'))

看到这里,如果你觉得这篇文章对你理解KMP算法有帮助的话呢,不妨关注我,我会持续更新各种有用的东西。我的个人公众号是【程序小员】,也欢迎你的关注哦!

我是落阳,谢谢你的到访~

真的有这么丝滑吗?近日国外一小哥深入研究了KMP算法……的更多相关文章

  1. 纯css就能实现可点击切换的轮播图,feel起来很丝滑

    前言 轮播图经常会在项目里用到,但是实际上用到的轮播图都是比较简单的,没有复杂的特效,这个时候如果去引入swiper那些库的话,未免就有点杀鸡焉用牛刀了. 所以不如自己手写一个,而今天我要分享的一种写 ...

  2. 使用 CSS3 打造一组质感细腻丝滑的按钮

    CSS3 引入了众多供功能强大的新特性,让设计和开发人员能够轻松的创作出各种精美的界面效果.下面这些发出闪亮光泽的按钮,很漂亮吧?把鼠标悬停在按钮上,还有动感的光泽移动效果. 温馨提示:为保证最佳的效 ...

  3. OC语言编写:为视图添加丝滑的水波纹

    先看一下最终效果图: 首先我们可以把如此丝滑的水波纹拆分一下下: 一条规律的曲线. 曲线匀速向右移动. 曲线下方的位置用颜色填充. 于是先来一条曲线吧. 对于需要产生波动如此规律的曲线,我们首先想到的 ...

  4. 让你的app体验更丝滑的11种方法!冲击手机应用榜单Top3指日可待

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由WeTest质量开放平台团队发表于云+社区专栏 一款app除了要有令人惊叹的功能和令人发指交互之外,在性能上也应该追求丝滑的要求,这样 ...

  5. jQuery和css3控制箭头丝滑旋转

    问题: 我们经常会遇见点击一个小三角使之丝滑的旋转180度上下旋转,怎么实现呢,需要css3搭配jq 来处理 如图:1.点击前 2.点击后(效果丝滑旋转)                 1.html ...

  6. 一分钟小知识:scroll-behavior 让你的页面导航滚动更丝滑~

    中午在[掘金]潜水摸鱼,看到这一个沸点,个人已经撸出特效: 下面放上  作者 的 掘金 地址  #掘金沸点# https://juejin.im/pin/5d649eaaf265da19752533d ...

  7. 《你还在写sql语句吗?》人生苦短,进入MybatisPlus的丝滑体验

    一.发展历程 依稀记得大学期间,类中写sql语句的日子,一个sql语句占据了大部分时间,到后来hibernate的出现算是解决了这一痛点.工作 后,我们又接触到了mybatis这样的框架,瞬间感觉这个 ...

  8. 『CDN』让你的网站访问起来更加柔顺丝滑

    我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  9. Node更丝滑的打开方式

    Node更丝滑的打开方式 1. 使用背景 最近前端的一个项目,使用gulp作为工程化.在运行过程中出现如下错误 gulp[3192]: src\node_contextify.cc:628: Asse ...

随机推荐

  1. 20190925-01安装redis 000 022

    1.将redis压缩包放入Linux系统有4种办法. 第一:如果安装了VMware Tools工具可以直接进行拖拽 第二:在windows中找到配置好的共享文件夹将redis压缩包放入其中,在Linu ...

  2. 我的T440p出现怪事情了

    装上系统后,再稍微装些软件,或是打补丁,升级驱动什么的,再重启就起不来了. 也就是说,装一次系统只能好一次,关机或是重启就启动不了了,现象是在黑屏界面转两下就转不动了. 鼓捣一个周末也无效,昨天上系统 ...

  3. whlie do-whlie

    switch语句  用于根据多个不同条件执行不同动作.   while 循环         while循环基本语法:    条件初始化;   while(条件表达式){     //条件表达式就是判 ...

  4. leetcode刷题-64最小路径和

    题目 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入:[  [1,3,1],  [1,5, ...

  5. Mybatis注解开发相关

    一.项目构建 1.Java项目目录结构 2.在domain包下创建User实体类 package sun.domain; import java.io.Serializable; import jav ...

  6. burpsuite破解版2.0.11下载和部署

    Burpsuite破解版下载: 链接:https://pan.baidu.com/s/1qVdrCogMN5OrEa8_zrXcEg 提取码:k7cb 一.安装步骤: 1.双击打开注册机 2.点击Ru ...

  7. 漏洞扫描工具acunetix12会遇到的问题

    1.如果安装好之后,打开工具时显示无法访问,首先去看任务管理器当中,acunetix的服务是否启动了 2.如果忘记了账号密码,可以在安装目录下,双击ChangePassword.exe进行重置密码

  8. shellcode生成框架

    因为vs编译后自己会生成很多东西,我们稍微配置下 先获取kernel32基址 __declspec(naked) DWORD getKernel32() { __asm { mov eax, fs:[ ...

  9. 正则表达式基础(Regular Expression)

    正则表达式简介 n  为什么需要正则表达式? q  文本的复杂处理. n  正则表达式的优势和用途? q  一种强大而灵活的文本处理工具: q  提供了一种紧凑的.动态的方式,能够以一种完全通用的方式 ...

  10. 刷题[WUSTCTF2020]CV Maker

    解题思路 好家伙,打开一看像是cms,又看名字CV Maker.我以为直接要搜cve打了.搜了一会发现没什么啊.那先正常做把. 注册 注册成功后这里报错,猜测可能有注入点.先放在这里,继续登陆.发现是 ...