字符串匹配究极大招【KMP】:带你一步步从原理到构建
前言
一文带你了解如何去理解并实现KMP算法。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于 代码随想录 同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!
KMP原理
首先来知道什么是KMP,KMP是由三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP。其主要是应用在字符串匹配上面的。KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。那么通过什么方式可以利用已经匹配的信息呢,这时候就需要next数组了。next数组本质上就是一个前缀表。
什么是前缀表
在弄清楚什么是前缀表的时候,我们得先知道什么是前缀、后缀。前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
举个例子来说哈,对于字符串abcdfe来说,abc子串的所有前缀为a和ab,所有后缀为b和bc
那么前缀表的作用是什么呢?前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。如图所示,当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】:带你一步步从原理到构建的更多相关文章
- sdut 2125串结构练习--字符串匹配【两种KMP算法】
串结构练习——字符串匹配 Time Limit: 1000ms Memory limit: 65536K 有疑问?点这里^_^ 题目链接:http://acm.sdut.edu.cn/sduto ...
- 字符串匹配-BF算法和KMP算法
声明:图片及内容基于https://www.bilibili.com/video/av95949609 BF算法 原理分析 Brute Force 暴力算法 用来在主串中查找模式串是否存以及出现位置 ...
- 洛谷 P3375 【模板】KMP字符串匹配 || HDU 1686 Oulipo || kmp
HDU-1686 P3375 kmp介绍: http://www.matrix67.com/blog/archives/115 http://www.cnblogs.com/SYCstudio/p/7 ...
- 字符串KMP——用途广泛的字符串匹配算法 + 扩展KMP——特殊定义的字符串匹配
引 入 引入 引入 " SY 和 WYX 在看毛片.(几 毛 钱买到的动作 片,毛 片) WYX 突然想回味一个片段,但是只记得台词里面有一句挺长的 " ∗ ∗ ∗ ∗ **** ...
- KMP字符串匹配 简单理解
http://www.cnblogs.com/c-cloud/p/3224788.html 字符串匹配,长串长度为m,子串长度为n 则,暴力破解的复杂度为o(m*n) 如果用kmp匹配,则复杂度为o( ...
- zstu.4194: 字符串匹配(kmp入门题&& 心得)
4194: 字符串匹配 Time Limit: 1 Sec Memory Limit: 128 MB Submit: 206 Solved: 78 Description 给你两个字符串A,B,请 ...
- 字符串匹配KMP算法详解
1. 引言 以前看过很多次KMP算法,一直觉得很有用,但都没有搞明白,一方面是网上很少有比较详细的通俗易懂的讲解,另一方面也怪自己没有沉下心来研究.最近在leetcode上又遇见字符串匹配的题目,以此 ...
- 字符串匹配的KMP算法
~~~摘录 来源:阮一峰~~~ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”? 许 ...
- {Reship}{KMP字符串匹配}
关于KMP字符串匹配的介绍和归纳,作者的思路非常清晰,推荐看一下 http://blog.csdn.net/v_july_v/article/details/7041827
- 字符串匹配的KMP算法详解及C#实现
字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD" ...
随机推荐
- [学习笔记]最近公共祖先(LCA)之倍增算法
1.定义 倍增法,顾名思义就是翻倍.它能够大大地优化时间复杂度.这个方法在很多算法中均有应用,例如求 LCA(最近公共祖先).(大雾) 2.框架 如下图,我们想找 \(4\) 和 \(8\) 的最近公 ...
- 独立看门狗IWDG
一.简介 STM32F10xxx内置两个看门狗(独立看门狗是12位递减计数器,窗口看门狗是7位递减计数器),提供了更高的安全性.时间的精确性和使用的灵活性.两个看门狗设备(独立看门狗和窗口看门 ...
- 华为交换机VLAN配置
一.静态VLAN划分 1.查看接口信息 # 查看接口信息 display interfaces # 查看接口状态和配置的简要信息 display interfaces brief 2.静态VLAN划分 ...
- 解决当前标识(IIS APPPOOL\XXXX)没有对“C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files”的写访问权限的问题
1.问题描述 在Windows Server2019数据中心版中搭建IIS项目,访问的时候出现如下所示的错误: 当前标识(IIS APPPOOL\XXXX)没有对"C:\Windows\Mi ...
- Centos7部署DVWA靶场
Centos7部署DVWA靶场 DVWA 款开源的渗透测试漏洞练习平台,其中内含xs SQL注入. 文件上传.文件包含. CSRF和暴力破解等各个难度的测试环境. 安装httpd及其相关的组件 y ...
- Q:Linux下处理^M字符
Linux下处理^M字符,很多时候windows环境中编辑过的文件,在Linux下经常会出现^M字符,可以通过以下方式处理该字符. 方法一:vim命令打开文件,然后在vim命令模式下输入以下内容: 1 ...
- Luogu P2824 排序 题解 [ 紫 ] [ 线段树 ] [ 二分 ] [ adhoc ]
排序:二分线段树神仙好题. trick 我们可以二分值域,然后把大于等于它的数标记成 \(1\),其他标记为 \(0\)(有些题需要标记成 \(-1\) ),然后根据这个来 check 方案是否可行, ...
- C# 手机号码隐藏中间四位
C# 隐藏手机号码中间四位数字 使用正则表达式隐藏手机号中间四位 if (!string.IsNullOrWhiteSpace(txtPhone.Text) && txtPhone.T ...
- docker - [15] springboot微服务打包docker镜像
步骤: 1.构建Springboot项目 2.打包应用 3.编写dockerfile 4.构建docker镜像 5.发布运行 一.构建Springboot项目 (1)创建一个SpringBoot(以下 ...
- Hive - 多种表类型的CURD测试
关于torc.textfile.orc.es.hyperdrive表的CURD测试 TORC(支持事务的orc表)测试 TORC(分区表)测试 TEXTFILE 表测试 ORC 表测试 ES(Elas ...