前言

一文带你了解如何去理解并实现KMP算法。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于 代码随想录 同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!

KMP原理

首先来知道什么是KMP,KMP是由三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP。其主要是应用在字符串匹配上面的。KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。那么通过什么方式可以利用已经匹配的信息呢,这时候就需要next数组了。next数组本质上就是一个前缀表。

什么是前缀表

在弄清楚什么是前缀表的时候,我们得先知道什么是前缀、后缀。前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串

举个例子来说哈,对于字符串abcdfe来说,abc子串的所有前缀为aab,所有后缀为bbc

那么前缀表的作用是什么呢?前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。如图所示,当b和f不匹配的时候,不会从头开始,而是会跳到b来进行匹配。所以到这我们就可以知道了什么是前缀表:即记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀

如何构建前缀表

长度为前1个字符的子串a,最长相同前后缀的长度为0。(注意字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)因为其没有前后缀,所以第一个字符必定为0.

长度为前2个字符的子串aa,最长相同前后缀的长度为1。其前缀a和其后缀a最长相等为1

长度为前3个字符的子串aab,最长相同前后缀的长度为0。无最长相等的前后缀

以此类推: 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。

那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:

可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。

next数组

说到这,我们就得回到next数组了。那么做字符串匹配的时候,很多时候都是以next数组进行回退的,那么next数组与前缀表又是什么关系呢?其实本质上next数组就是前缀表,当然有些人对前缀表有不同的处理,所以next数组的形式可能会有不同,但其本质上都是一样的,逻辑都是相通的。这里直接讲述让前缀表作为next数组(别的处理有全部减一或者整体向右边移动一位)。

这里我们来讲述如何构建next数组。(以python代码为例)

构建next数组有三个重要步骤:

  • 初始化
  • 处理前后缀不相同的情况
  • 处理前后缀相同的情况

初始化:初始化的不同,决定了你的next数组的形式。我们这里将定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。我们这里将j初始化为0,并且将next数组的第一位初始化为0.

处理前后缀不相同的情况:因为j初始化为0,那么i就从1开始,进行s[i] 与 s[j]的比较。所以遍历模式串s的循环下标i 要从 1开始,如果 s[i] 与 s[j]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。怎么回退呢?next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。那么 s[i] 与 s[j] 不相同,就要找 j前一个元素在next数组里的值(就是next[j-1])。

处理前后缀相同的情况:如果 s[i] 与 s[j ] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。

最后其整体代码如下:

def getNext(self, next: List[int], s: str) -> None:
j = 0
next[0] = 0
for i in range(1, len(s)):
while j > 0 and s[i] != s[j]:
j = next[j - 1]
if s[i] == s[j]:
j += 1
next[i] = j

使用next数组做匹配

好了,最重要的来了,前面讲述了那么多,其根本就是为了字符串匹配做工作。我们要在文本串s里 找是否出现过模式串t。以下是具体的思路及其步骤。

  • 首先定义两个下标j 指向模式串起始位置,i指向文本串起始位置。

  • 那么j初始值依然为0,为什么呢? 依然因为next数组里记录的起始位置为0。

  • i就从0开始,遍历文本串。

  • 接下来就是 s[i] 与 t[j ] (因为j从0开始的) 进行比较。

  • 如果 s[i] 与 t[j ] 不相同,j就要从next数组里寻找下一个匹配的位置。

  • 如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动.

  • 如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。

  • 要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。

并且我们来看其时间复杂度,用暴力法解题即两个for循环,时间复杂度为O($n^2$),而用KMP算法来解题,其时间复杂度为O($m+n$),其m和n分别为字符串s和模式串t的长度,通常模式串t不会很大,所以一般是可以忽略的。

所以最后完整的代码为:

def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = 0
for i in range(len(haystack)):
while j > 0 and haystack[i] != needle[j]:
j = next[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i - len(needle) + 1
return -1

实战演练

28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)

class Solution:
def getNext(self, next: List[int], s: str) -> None:
j = 0
next[0] = 0
for i in range(1, len(s)):
while j > 0 and s[i] != s[j]:
j = next[j - 1]
if s[i] == s[j]:
j += 1
next[i] = j def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = 0
for i in range(len(haystack)):
while j > 0 and haystack[i] != needle[j]:
j = next[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i - len(needle) + 1
return -1

字符串匹配究极大招【KMP】:带你一步步从原理到构建的更多相关文章

  1. sdut 2125串结构练习--字符串匹配【两种KMP算法】

    串结构练习——字符串匹配 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目链接:http://acm.sdut.edu.cn/sduto ...

  2. 字符串匹配-BF算法和KMP算法

    声明:图片及内容基于https://www.bilibili.com/video/av95949609 BF算法 原理分析 Brute Force 暴力算法 用来在主串中查找模式串是否存以及出现位置 ...

  3. 洛谷 P3375 【模板】KMP字符串匹配 || HDU 1686 Oulipo || kmp

    HDU-1686 P3375 kmp介绍: http://www.matrix67.com/blog/archives/115 http://www.cnblogs.com/SYCstudio/p/7 ...

  4. 字符串KMP——用途广泛的字符串匹配算法 + 扩展KMP——特殊定义的字符串匹配

    引 入 引入 引入 " SY 和 WYX 在看毛片.(几 毛 钱买到的动作 片,毛 片) WYX 突然想回味一个片段,但是只记得台词里面有一句挺长的 " ∗ ∗ ∗ ∗ **** ...

  5. KMP字符串匹配 简单理解

    http://www.cnblogs.com/c-cloud/p/3224788.html 字符串匹配,长串长度为m,子串长度为n 则,暴力破解的复杂度为o(m*n) 如果用kmp匹配,则复杂度为o( ...

  6. zstu.4194: 字符串匹配(kmp入门题&& 心得)

    4194: 字符串匹配 Time Limit: 1 Sec  Memory Limit: 128 MB Submit: 206  Solved: 78 Description 给你两个字符串A,B,请 ...

  7. 字符串匹配KMP算法详解

    1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有比较详细的通俗易懂的讲解,另一方面也怪自己没有沉下心来研究.最近在leetcode上又遇见字符串匹配的题目,以此 ...

  8. 字符串匹配的KMP算法

    ~~~摘录 来源:阮一峰~~~ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”? 许 ...

  9. {Reship}{KMP字符串匹配}

    关于KMP字符串匹配的介绍和归纳,作者的思路非常清晰,推荐看一下 http://blog.csdn.net/v_july_v/article/details/7041827

  10. 字符串匹配的KMP算法详解及C#实现

    字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD" ...

随机推荐

  1. 从一指禅到无重复字符:最长子串问题的优雅解法|LeetCode 3 无重复字符的最长子串

    LeetCode 3 无重复字符的最长子串 点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中) 生活中的算法 你是否玩过"一指禅"游戏?就是沿 ...

  2. 爬虫基础知识及scrapy框架使用和基本原理

    爬虫 一.异步IO 线程:线程是计算机中工作的最小单元 ​ IO请求(IO密集型)时多线程更好,计算密集型进程并发最好,IO请求不涉及CPU 自定义线程池 进程:进程默认有主线程,可以有多线程共存,并 ...

  3. PC端自动化测试实战教程-1-pywinauto 环境搭建(详细教程)

    1.简介 之前总有人在群里或者私信留言问:Windows系统安装的软件如何自动化测试呢?因为没有接触过或者遇到过,所以说实话宏哥当时也不清楚怎么实现,怎么测试.然而在一次偶然的机会接触到了Python ...

  4. 性能测试-Oceanus 测试FLink mysql到Iceberg性能

    一.任务依赖信息 1.mysql测试库信息 地址:127.0.0.1.gomysql_bdg_test 库:bdg_test 表:order_info1 2.iceberg库 hive地址:thrif ...

  5. Java 中堆内存和栈内存上的数据分布和特点

    博客:https://www.emanjusaka.com 博客园:https://www.cnblogs.com/emanjusaka 公众号:emanjusaka的编程栈 by emanjusak ...

  6. ORACLE 分页排序后的数据重复或缺失问题

    今天一大早业务人员就反映说用户导出的订单数据,有好几单是重复,并且缺失了某一单. 第一步:查询数据表.表里实际数据没有重复,也没有缺失.那么就可能是导出过程出错了(因为是异步分页导出,所以最先怀疑这部 ...

  7. 大模型工具KTransformer的安装

    技术背景 前面写过几篇关于DeepSeek的文章,里面包含了通过Ollama来加载模型,以及通过llama.cpp来量化模型(实际上Llama.cpp也可以用来加载模型,功能类似于Ollama).这里 ...

  8. 使用 Visual Paradigm 的业务流程模型和符号 (BPMN) 综合指南

    业务流程模型和符号 (BPMN) 是一种用于建模和记录业务流程的标准化图形符号.它被广泛采用,因为它能够提供一种清晰.通用的语言,所有利益相关者(业务分析师.技术开发人员和管理人员)都能理解.Visu ...

  9. Linux Centos7 下使用yum安装redis

    更改yum源 由于CentOS官方yum源里面没有Redis,这里我们需要安装一个第三方的yum源,这里用了Fedora的epel仓库: yum install epel-release 安装redi ...

  10. Vim编辑windows格式文件出现的[noeol][dos]的含义、解决方法及方法解释

    文章目录 前言 [dos] [noeol] 前言 最近想要将保存再windows的文件传到linux上,传进去保存文件之后,用vim打开发现在文件的底下出现了[dos] [noeol]这两个标志.然后 ...