字符串匹配究极大招【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" ...
随机推荐
- nginx basic验证
打开个生成htpasswd的网站 输入信息生成结果 将结果保存到nginx一个文件里面 修改nginx的conf文件 auth_basic "webA"; #这个"&qu ...
- 9. SpringCloud Alibaba Sentinel 流量控制、熔断降级、系统负载,热点规则的部署设置讲解
9. SpringCloud Alibaba Sentinel 流量控制.熔断降级.系统负载,热点规则的部署设置讲解 @ 目录 9. SpringCloud Alibaba Sentinel 流量控制 ...
- uni-app路由跳转
navigateTo redirectTo (1)保留当前页面,跳转到应用内的某个页面,使用uni.navigateBack可以返回到原页面 (如果A->B来回频繁切换,不要A B两个方法都使用 ...
- 手把手教你编写自定义Categraf插件
本文分享自天翼云开发者社区<手把手教你编写自定义Categraf插件>,作者:任****佳 Categraf 是一个监控采集 Agent,类似 Telegraf.Grafana-Agent ...
- LangChain基础篇 (03)
LangChain 核心模块学习:Memory 大多数LLM应用都具有对话界面.对话的一个重要组成部分是能够引用先前在对话中介绍过的信息.至少,一个对话系统应该能够直接访问一些过去消息的窗口.更复杂的 ...
- 5.main.js配置
1.根目录新建api文件夹 api文件夹分mock(存放虚拟json)和urls(api请求链接) urls 中新建index.js来汇总按分类拆分的url请求文件 2.添加api配置 imp ...
- Doris插入数据底层存储测试
建表语句 CREATE TABLE IF NOT EXISTS base_site_test( site_id INT DEFAULT '10', city_code INT, user_name V ...
- C#下.NET配置文件使用(二)
app.config 与 Settings.settings 用VC#创建一个GUI程序后,就会有一个 Settings.settings 文件. 一旦我们通过VC#给它添加值后,在工程目录下会生成一 ...
- DexExpress Wpf BackstageItemWithImage
参考链接: https://docs.devexpress.com/WPF/DevExpress.Xpf.Ribbon.BackstageItemWithImage.GlyphStyle 设置 Bac ...
- 如何修改JSONObject 的值
问 题 { "result": { "total": "3", "shops": [ { "shopId&qu ...