Knuth-Morris-Pratt 字符串查找算法(常简称为 “KMP算法”)是在一个“主文本字符串”S 内查找一个“词”W 的出现,通过观察发现,在不匹配发生的时候这个词自身包含足够的信息来确定下一个匹配将在哪里开始,以此避免对以前匹配过的字符重新检查。

举个例子: leetcode中28. Implement strStr()

返回在haystack中第一次出现Needle的索引,如果Needle不是haystack的一部分,则返回-1。

Example 1:

Input: haystack = "hello", needle = "ll"                                                                                                                              Output: 2

字符串搜索算法的基本分类

m为模式的长度,n为可搜索文本的长度,k = |Σ| 是字母表的大小。

Algorithm Preprocessing time Matching time[1] Space
Naïve string-search algorithm none Θ(nm) none
Rabin–Karp algorithm Θ(m) average Θ(n + m),
worst Θ((n−m)m)
O(1)
Knuth–Morris–Pratt algorithm Θ(m) Θ(n) Θ(m)
Boyer–Moore string-search algorithm Θ(m + k) best Ω(n/m),
worst O(mn)
Θ(k)
Bitap algorithm (shift-orshift-andBaeza–Yates–Gonnet) Θ(m + k) O(mn)  
Two-way string-matching algorithm Θ(m) O(n+m) O(1)
BNDM (Backward Non-Deterministic Dawg Matching) O(m) O(n)  
BOM (Backward Oracle Matching) O(m) O(mn)

在过去的几天里,我一直在阅读Knuth-Morris-Pratt字符串搜索算法的各种解释。出于某种原因,没有一个解释是为我做的。一旦我开始阅读“......前缀后缀的前缀”,我就不停地将头撞在砖墙上。

最后,在一遍又一遍地读了同一段CLRS(算法导论)大约30分钟后,我决定坐下来,做一堆例子,然后把它们弄清楚。我现在理解算法,并且可以解释它。对于那些像我一样思考的人,这是用我自己的话说的。作为旁注,我不打算解释为什么它比na“字符串匹配更有效率; 那是在一个解释的非常清楚众多的地方。我将解释它是如何工作的,正如我的大脑所理解的那样。

部分匹配表

当然,KMP的关键是部分匹配表。我和理解KMP之间的主要障碍是我没有完全掌握部分匹配表中的值到底意味着什么。我现在试着用最简单的话来解释它们。

这是“abababca”模式的部分匹配表:

char:  | a | b | a | b | a | b | c | a |
index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

如果我有一个八字符模式(在​​这个例子的持续时间内让我们说“abababca”),我的部分匹配表将有八个单元格。                        如果我正在查看表格中的第八个和最后一个单元格,我对整个模式(“abababca”)感兴趣。                                                        如果我正在查看表格中的第七个单元格,我只对模式中的前七个字符感兴趣(“abababc”)。第八个(“a”)是无关紧要的,可以从建筑物或其他东西上掉下来。                                                                                                                                                      如果我正在看表中的第六个单元格...你明白了。请注意,我还没有谈到每个单元格的含义,而是它所指的内容。

现在,为了谈论其含义,我们需要了解正确的前缀正确的后缀

正确的前缀:字符串中的所有字符,其中一个或多个字符被截断。“S”,“Sn”,“Sna”和“Snap”都是“Snape”的正确前缀。

正确的后缀:字符串中的所有字符,一个或多个字符在开头处截断。“agrid”,“grid”,“rid”,“id”和“d”都是“Hagrid”的正确后缀。

考虑到这一点,我现在可以给出部分匹配表中值的一句话含义:

(子)模式中与同一(子)模式中的正确后缀匹配的最长正确前缀的长度。

让我们来看看我的意思。

假设我们正在寻找第三个细胞。从上面你会记得,这意味着我们只对前三个字符(“aba”)感兴趣。在“aba”中,有两个正确的前缀(“a”和“ab”)和两个正确的后缀(“ba”和“a”)。正确的前缀“ab”与两个正确的后缀中的任何一个都不匹配。但是,正确的前缀“a”与正确的后缀“a”匹配。因此,在这种情况下,匹配正确后缀的最长正确前缀的长度是1。

让我们尝试一下第四组。在这里,我们对前四个字符(“abab”)感兴趣。我们有三个正确的前缀(“a”,“ab”和“aba”)和三个正确的后缀(“bab”,“ab”和“b”)。这次,“ab”在两者中,并且是两个字符长,因此单元格4获得值2。

仅仅因为这是一个有趣的例子,让我们也尝试第五小组,这涉及“ababa”。我们有四个正确的前缀(“a”,“ab”,“aba”和“abab”)和四个正确的后缀(“a”,“ba”,“aba”和“baba”)。现在,我们有两个匹配:“a”和“aba”都是正确的前缀和正确的后缀。由于“aba”比“a”长,所以它获胜,而第五个小组获得值3。

让我们跳到第7单元格(倒数第二个单元格),它关注模式“abababc”。即使没有列举所有正确的前缀和后缀,显然也不会有任何匹配; 所有后缀都以字母“c”结尾,并且没有前缀。由于没有匹配,单元格7变为0。

最后,让我们看看第8单元,它关注整个模式(“abababca”)。由于它们都以“a”开头和结尾,我们知道它的值至少为1.但是,它就是它的结束点; 在长度为2或更高时,所有后缀都包含ac,而只有最后一个前缀(“abababc”)。这个七个字符的前缀与七个字符的后缀(“bababca”)不匹配,因此单元格8的前缀为1。

如何使用部分匹配表

当我们找到部分匹配时,我们可以使用部分匹配表中的值来跳过(而不是重做不必要的旧比较)。该公式的工作方式如下:

如果找到长度为partial_match_length的部分匹配 table[partial_match_length] > 1,我们可以跳过前面的partial_match_length - table[partial_match_length - 1]字符。

假设我们将“abababca”模式与“bacbababaabcbab”相匹配。这里是我们的部分匹配表,以便于参考:

char:  | a | b | a | b | a | b | c | a |
index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

我们第一次获得部分匹配是在这里:

bacbababaabcbab|abababc

这是partial_match_length为1. table[partial_match_length - 1](或table[0])的值为0,因此我们不会先跳过任何一个。我们得到的下一个部分匹配是:

bacbababaabcbab|||||abababca

这是partial_match_length为5. table[partial_match_length - 1](或table[4])的值为3.这意味着我们可以跳过前面partial_match_length - table[partial_match_length - 1](或5 - table[4]5 - 32)字符:

// x denotes a skipbacbababaabcbabxx|||abababca

这是partial_match_length 3. table[partial_match_length - 1](或table[2])的值是1.这意味着我们可以跳过前面partial_match_length - table[partial_match_length - 1](或3 - table[2]3 - 12)字符:

// x denotes a skipbacbababaabcbabxx|abababca

此时,我们的模式比文本中的其余字符长,所以我们知道没有匹配。

它不是KMP的详尽解释或正式证明; 这是我大脑中的一个散步,我发现的部分非常详细地拼写出来。 如果您有任何疑问或注意到我搞砸了,请发表评论; 也许我们都会学到一些东西。

原文地址:http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/

搜索算法的伪代码描述

上面的示例包含算法的所有元素。 目前,我们假设存在“部分匹配”表T,如下所述,其表示在当前的匹配以不匹配结束的情况下我们需要在哪里寻找新匹配的开始。

构造T的条目使得如果我们具有从S [m]开始的匹配,其在比较S [m + i]和W [i]时失败,则下一个可能的匹配将从索引m + i开始 -  T [ 我在S中(也就是说,T [i]是在不匹配后我们需要做的“回溯”量)。 这有两个含义:首先,T [0] = -1,表示如果W [0]不匹配,我们就不能回溯,只需检查下一个字符; 第二,尽管下一个可能的匹配将从索引m + i  -  T [i]开始,如上例所示,我们不需要在此之后实际检查任何T [i]字符,以便我们继续从W搜索[T[I]]。 以下是KMP搜索算法的示例伪代码实现。

algorithm  kmp_search:输入:一个字符数组,S(要搜索的文本)一个字符数组,W(单词搜索)输出:一个整数数组,P(在S中找到W的位置)一个整数, nP(位置数)定义变量:一个整数,j←0(S中当前字符的位置)一个整数,k←0(W中当前字符的位置)一个整数数组,T(表,在其他地方计算)让nP←0 while j < length(S) doif W[k] = S[j] thenlet j ← j + 1let k ← k + 1if k = length(W) then(occurrence found, if only first occurrence is needed, m ← j - k  may be returned here)let P[nP] ← j - k, nP ← nP + 1let k ← T[k] (T[length(W)] can't be -1)elselet k ← T[k]if k < 0 thenlet j ← j + 1let k ← k + 1

Knuth-Morris-Pratt算法(KMP)相关推荐

  1. Python:实现knuth morris pratt(KMP)算法(附完整源码)

    Python:实现knuth morris pratt(KMP)算法 from __future__ import annotationsdef kmp(pattern: str, text: str ...

  2. Python:实现前缀Knuth–Morris–Pratt 算法(附完整源码)

    Python:实现前缀Knuth–Morris–Pratt 算法 def prefix_function(input_string: str) -> list:# list for the re ...

  3. Prefix function. Knuth–Morris–Pratt algorithm

    Prefix function. Knuth–Morris–Pratt algorithm Prefix function definition You are given a string \(s\ ...

  4. KMP子串匹配算法(Knuth–Morris–Pratt algorithm)

    假设原串S,模式串M 简单的说这个算法的主要思想就是利用模式串M自身的匹配性质,快速找到下一跳的位置. 而这个下一跳的位置只与模式串相关,所以可以根据这个串制作一个next的表T,来指示出一旦匹配不成 ...

  5. 字符串匹配算法(BF算法KMP算法)

    字符串匹配算法 暴力匹配(BF)算法 KMP算法 next数组 求next数组的练习 next数组的优化(nextval数组) 练习 暴力匹配(BF)算法 BF算法,即暴力(Brute Force)算 ...

  6. 字符串的模式匹配--BF算法KMP算法

    BF算法是基于主串指针回溯,重新与子串进行逐字符进行比较,主串为S什么要进行回溯呢,原因在于模式P中存在相同的字符或者说由字符(串)存在重复(模式的部分匹配性质),设想如果模式P中字符各不相同,主串就 ...

  7. 二叉树的遍历(递归、栈、morris莫里斯算法)三种方法

    二叉树的前序遍历 递归 class TreeNode{TreeNode left;TreeNode right;int val;public TreeNode(){}public TreeNode(i ...

  8. 字符串算法——KMP匹配及Next数组

    KMP是单模匹配算法,即在一段长度为n的文本串中搜索一个长度为m的模式串,算法复杂度为O(n+m),差不多是这类算法能达到的最优复杂度. 朴素的模式匹配算法 在处理这类问题时,最简单的方法便是暴力匹配 ...

  9. Knuth 洗牌算法

    核心思想 洗牌算法(Knuth shuffle算法):对于有n个元素的数组来说,为了保证洗牌的公平性,应该要能够等概率的洗出n!种结果. 举例解释如下: 开始数组中有五个元素: 在前五个数中随机选一个 ...

  10. 这或许是讲解 Knuth 洗牌算法最好的文章

    点击蓝色"五分钟学算法"关注我哟 加个"星标",一起学算法 作者 | liuyubobobo 来源公众号 | 是不是很酷 首先来思考一个问题: 设计一个公平的洗 ...

最新文章

  1. Spring事务管理-传播行为-隔离级别
  2. 【Linux】一步一步学Linux——unset命令(202)
  3. 工作原理_逆变器工作原理
  4. java判断题_【Java判断题】请大神们进来看下、这些判断题你都知道多少~
  5. 图片优化_网站里的图片应该如何优化
  6. 发动机压缩比怎么计算公式_怎么判断发动机有积碳,发动机积碳多的症状有哪些...
  7. Linux之crontab命令
  8. Java基础6:代码块与代码加载顺序
  9. 微信小程序获取手机号 前台+php后台
  10. android 不限速迅雷,安卓iOS,Windows和Mac四大系统迅雷不限速神器,今天全部解决了...
  11. 数据结构与算法(python版)
  12. springboot 代码自动生成器
  13. matlab中创建txt文件,在MATLAB中创建填充了任意数据的大型txt文件
  14. 无线之minidwep-gtk
  15. 在ubuntu9.04中安装电视卡用到的资料
  16. html里表格做斜线表头,word2010怎么绘制斜线表头
  17. web支付开发报错:无效的AppID参数
  18. 基于tensorflow和卷积神经网络的电影推荐系统的实现
  19. 第九章总结 java常用类
  20. windows模拟微信小程序_微信小程序的开发环境搭建(Windows版本)

热门文章

  1. android跑马灯会暂停,Android之跑马灯失焦停止问题
  2. 2022-2028年中国化学药行业市场研究及前瞻分析报告
  3. java框架学习顺序
  4. vue-element-表格 Excel 【导出】功能
  5. 变量---超市买苹果
  6. ventuz连接mysql8.0操作
  7. 卓越人生奥秘:成就事业者十大必备素质
  8. [VNCTF] insterestingPHP
  9. 企业最佳Node.js 应用案例分享
  10. pyCharm字体放大缩小快捷键