0

0

带你轻松理解KMP算法

little bottle

little bottle

发布时间:2019-04-30 14:25:40

|

2119人浏览过

|

来源于博客园

转载

kmp(the knuth-morris-pratt algorithm)算法用于字符串匹配,从字符串中找出给定的子字符串。但它并不是很好理解和掌握。而理解它概念中的部分匹配表,是理解 kmp 算法的关键。

这里的讨论绕开其背后晦涩难懂的逻辑,着重从其运用上来理解它。

字符串查找

比如从字符串 abcdef 中找出 abcdg 子字符串。

朴素的解法,我们可以这样做,

  • 分别取出第一位进行匹配,如果相同再取出各自的第二位。
  • 如果不同,则将索引后移一位,从总字符串第二位开始,重复步骤一。

这种朴素解法的弊端在于,每次匹配失败,索引只后移一位,有很多冗余操作,效率不高。

在进行第一轮匹配中,即索引为 0 时,我们能够匹配出前四个字符 abcd 是相等的,后面发现想要的 g 与真实的 e 不符,标志着索引为 0 的情况匹配失败,开始查看索引为 1 时,但因为我们在第一轮匹配中,已经知道了总字符串中前四个字符的长相,但还是需要重复地挨个进行匹配。

部分匹配表/Partial Match Table

以长度为 8 的字符串 abababca,为例,其部分匹配表格为:

@@######@@

其中 value 行便是部分匹配表的值。

子集

对于上面示例字符串,假如我们观察第 index 为 2 的位置,那么我们得到了字符串的一个子集 aba,如果我们观察 index 为 7 的位置,那得到的是整个字符串,这点是很显然的。当我们观察的位置不同时,表示我们关注的字符串中的子集不同,因为子字符串发生了变化。

前缀 & 后缀

对于给定的字符串,从末尾开始去掉一个或多个字符,剩下的部分都叫作该字符串的真前缀(Proper prefix),后面简称前缀。这里「真」不是「真·前缀」的意思,联想一下数学里面集合的「真子集」。比如 banana,其前缀有:

  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">b</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">ba</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">ban</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">bana</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">banan</span>

同理,从首部开始,去掉一个或多个字条,剩下的部分是该字符串的真后缀(Proper suffix)。还是 banana,其后缀有:

  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">anana</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">nana</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">ana</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">na</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">a</span>

部分匹配值

可以看到,所有前缀和后缀在数量上是对称的,那么我们可以从前缀中找出一个,与后缀进行匹配,先不关心做这个匹配的意义。以最开始的文本 abababca 为例。

假如我们观察 index 为 2 的位置,此时子字符串为 aba,其前后缀分别为:

  • 前缀:aab
  • 后缀:baa

将前缀依次在后缀中去匹配,这里前后缀列表中能够匹配上的只有 a 这个子字符串,其长度为 1,所以将这个观测结果填入表中记下来,与开始看到的部分匹配表吻合了。

再比如来观察 index 为 3 的位置,此时得到的子字符串为 abab,此时的前后缀为:

  • 前缀:aababa
  • 后缀:bababb

此时可观察出其匹配项为 ab,长度为 2,也与上面部分匹配表中的值吻合。

再比如来观察 index 为 5 的位置,此时子字符串为 ababab,前后缀为:

  • 前缀:aababaababababa
  • 后缀:bababababbababb

然后拿前缀中每个元素与后缀中的元素进行匹配,最后找出有两个匹配项,

  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">ab</span>
  • <span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">abab</span>

我们取长的这个 abab,其长度为 4。

Sora
Sora

Sora是OpenAI发布的一种文生视频AI大模型,可以根据文本指令创建现实和富有想象力的场景。

下载

所以现在再来看上面的部分匹配表,一是能理解其值是怎么来的,二是能理解其表示的意义,即,所有前缀与后缀的匹配项中长度最长的那一个的长度。

当我们继续,进行到 index 为 6 时,子字符串为 abababc,可以预见,前后缀中找不到匹配。因为所有前缀都不包含 c,而所有后缀都包含 c。所以此时部分匹配值为 0。

再继续就到字符串末尾了,即整个字符串 abababca。也可以预见,因为所有前缀都以 a 开始,并且所有后缀都以 a 结尾,所以此时的部分匹配值最少为 1。继续会发现,因为后面的后缀开始有 c 的加入,使得后缀都包含 ca,而前缀中能够包含 c 的只有 abababc,而该长度 7 与同等长度的后缀 bababca 不匹配。至此就可以得出结论,匹配结果就是 1,没有更长的匹配了。

部分匹配表的使用

利用上面的部分匹配值,我们在进行字符串查找时,不必每次失败后只移动一位,而是可以移动多位,去掉一些冗余的匹配。这里有个公式如下:

If a partial match of length partial_match_length is found and table[partial_match_length] > 1, we may skip ahead partial_match_length - table[partial_match_length - 1] characters.

如果匹配过程中,匹配到了部分值为 partial_match_length,即目前找出前 partial_match_length 个字符是匹配的,将这个长度减一作为部分匹配表格中的 index 代入,查找其对应的 valuetable[partial_match_length-1],那么我们可以向前移动的步长为 partial_match_length - table[partial_match_length - 1]

下面是本文开始时的那个部分匹配表:

@@######@@

假设需要从 bacbababaabcbab 中查找 abababca,根据上面的公式我们来走一遍。

首次匹配发生在总字符串的第二个字符,

@@######@@

此时匹配的长度为 1,部分匹配表中索引为 1-1=0 的位置对应的部分匹配值为 0,所以我们可以向前移动的距离是 1-0 1。其实也相当于没有跳跃,就是正常的本次匹配失败,索引后移一位的情况。这里没有节省任何成本。

继续直到再次发生匹配,此时匹配到的情况如下:

@@######@@

现在匹配到的长度是 5,部分匹配表中 5-1=4 对应的部分匹配值为 3,所以我们可以向前移动 5-3=2,此时一下子就可以移动两位了。

@@######@@

此时匹配到的长度为 3, 查找到 table[partial_match_length-1] 即 index 为 2 对应的值为 1,所以可向前移动的距离为 

3-1=2。

@@######@@

此时我们需要查找的字符串其长度已经超出剩余可用来匹配的字符串了,所以可直接结束匹配,得到结论:没有查找到结果。

Javascript 中的实现

以下是来自 trekhleb/javascript-algorithms 中 JavaScript 版本的 KMP 算法实现:

相关教程:Javascript视频教程

@@######@@

时间复杂度

因为算法中涉及两部分字符串的线性对比,其时间复杂度为两字符串长度之和,假设需要搜索的关键词长度为 k,总字符串长度为 m,则时间复杂度为 O(k+m)。

<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">char:  | a | b | a | b | a | b | c | a |<br/>index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | <br/>value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |</span>
<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">char:  | a | b | a | b | a | b | c | a |<br/>index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | <br/>value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |</span>
<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">bacbababaabcbab |<br/> abababca</span>
<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">bacbababaabcbab    |||||<br/>    abababca</span>
<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">    上一次的位置    | 最新移动到的位置    | |bacbababaabcbab<br/>    xx|||<br/>      abababca</span>
<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">bacbababaabcbab<br/>      xx|<br/>        abababca</span>
<span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">//**<br/> * @see https://www.youtube.com/watch?v=GTJr8OvyEVQ<br/> * @param {string} word<br/> * @return {number[]}<br/> */<br/>function buildPatternTable(word) {<br/>  const patternTable = [0];<br/>  let prefixIndex = 0;<br/>  let suffixIndex = 1;<br/><br/>  while (suffixIndex < word.length) {<br/>    if (word[prefixIndex] === word[suffixIndex]) {<br/>      patternTable[suffixIndex] = prefixIndex + 1;<br/>      suffixIndex += 1;<br/>      prefixIndex += 1;<br/>    } else if (prefixIndex === 0) {<br/>      patternTable[suffixIndex] = 0;<br/>      suffixIndex += 1;</span><span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif"><br/></span><span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif">    } else {<br/>      prefixIndex = patternTable[prefixIndex - 1];<br/>    }<br/>  }<br/><br/>  return patternTable;<br/>}<br/><br/>/**<br/> * @param {string} text<br/> * @param {string} word<br/> * @return {number}<br/> */<br/>export default function knuthMorrisPratt(text, word) {<br/>  if (word.length === 0) {<br/>    return 0;</span><span style="font-family:Microsoft Yahei, Hiragino Sans GB, Helvetica, Helvetica Neue, 微软雅黑, Tahoma, Arial, sans-serif"><br/>  }<br/><br/>  let textIndex = 0;<br/>  let wordIndex = 0;<br/><br/>  const patternTable = buildPatternTable(word);<br/><br/>  while (textIndex < text.length) {<br/>    if (text[textIndex] === word[wordIndex]) {<br/>      // We've found a match.<br/>      if (wordIndex === word.length - 1) {<br/>        return (textIndex - word.length) + 1;<br/>      }<br/>      wordIndex += 1;<br/>      textIndex += 1;<br/>    } else if (wordIndex > 0) {<br/>      wordIndex = patternTable[wordIndex - 1];<br/>    } else {<br/>      wordIndex = 0;<br/>      textIndex += 1;<br/>    }<br/>  }<br/><br/>  return -1;<br/>}<br/></span>

作者最新文章

jq是指什么?

2019-05-31 16:04

vue是做什么的

2019-05-31 16:58

mysql能干什么

2019-05-31 17:15

怎么用cmd进入mysql

2019-05-31 17:24

mysql怎么输入

2019-05-31 17:41

mysql免费版好用么

2019-05-31 17:53

mysql慢查询是什么

2019-05-31 18:00

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
if什么意思
if什么意思

if的意思是“如果”的条件。它是一个用于引导条件语句的关键词,用于根据特定条件的真假情况来执行不同的代码块。本专题提供if什么意思的相关文章,供大家免费阅读。

839

2023.08.22

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

678

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

219

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1560

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

645

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1108

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

1062

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

187

2025.07.29

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

48

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SciPy 教程
SciPy 教程

共10课时 | 1.8万人学习

R 教程
R 教程

共45课时 | 7.4万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号