带你轻松理解KMP算法

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

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

字符串查找

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

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

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

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

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

部分匹配表/Partial Match Table

以长度为 8 的字符串 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 |

登录后复制登录后复制

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

子集

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

前缀 & 后缀

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

bbabanbanabanan

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

ananananaananaa

部分匹配值

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

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

前缀:a,ab后缀:ba,a

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

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

前缀:a,ab,aba后缀:bab,ab,b

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

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

前缀:a,ab,aba,abab,ababa后缀:babab,abab,bab,ab,b

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

ababab

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

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

当我们继续,进行到 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 代入,查找其对应的 value 即 table[partial_match_length-1],那么我们可以向前移动的步长为 partial_match_length – table[partial_match_length – 1]。

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

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 中查找 abababca,根据上面的公式我们来走一遍。

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

bacbababaabcbab |
abababca

登录后复制

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

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

bacbababaabcbab    |||||
abababca

登录后复制

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

    上一次的位置    | 最新移动到的位置    | |bacbababaabcbab
xx|||
abababca

登录后复制

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

3-1=2。

bacbababaabcbab
xx|
abababca

登录后复制

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

Javascript 中的实现

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

相关教程:Javascript视频教程

//**
* @see https://www.youtube.com/watch?v=GTJr8OvyEVQ
* @param {string} word
* @return {number[]}
*/
function buildPatternTable(word) {
 const patternTable = [0];
 let prefixIndex = 0;
 let suffixIndex = 1;

 while (suffixIndex    if (word[prefixIndex] === word[suffixIndex]) {
     patternTable[suffixIndex] = prefixIndex + 1;
     suffixIndex += 1;
     prefixIndex += 1;
   } else if (prefixIndex === 0) {
     patternTable[suffixIndex] = 0;
     suffixIndex += 1;

   } else {
     prefixIndex = patternTable[prefixIndex - 1];
   }
 }

 return patternTable;
}

/**
* @param {string} text
* @param {string} word
* @return {number}
*/
export default function knuthMorrisPratt(text, word) {
 if (word.length === 0) {
   return 0;

 }

 let textIndex = 0;
 let wordIndex = 0;

 const patternTable = buildPatternTable(word);

 while (textIndex    if (text[textIndex] === word[wordIndex]) {
     // We've found a match.
     if (wordIndex === word.length - 1) {
       return (textIndex - word.length) + 1;
     }
     wordIndex += 1;
     textIndex += 1;
   } else if (wordIndex > 0) {
     wordIndex = patternTable[wordIndex - 1];
   } else {
     wordIndex = 0;
     textIndex += 1;
   }
 }

 return -1;
}

登录后复制

时间复杂度

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

以上就是带你轻松理解KMP算法的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2731042.html

(0)
上一篇 2025年3月8日 00:23:51
下一篇 2025年3月3日 11:35:03

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • js是什么编程语言?

    js全称JavaScript,是一种具有函数优先的轻量级,直译式、解释型或即时编译型的高级编程语言,是一种属于网络的高级脚本语言;JavaScript基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式,如函数式编程。 本教…

    2025年3月8日
    200
  • js中怎么写ajax

    在JavaScript中使用ajax有两个作用: 1.让js去读服务器上面的数据. 2.无刷新的情况下读取服务器上面的数据,例如:验证账号和密码是否正确等. 对于网络请求我们知道有Get 和Post两种,它们之间的区别是什么呢? get方式…

    2025年3月8日
    200
  • JQuery属于什么语言

    jquery是基于JavaScript语言写出来的一个框架,它封装JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,但实质上还是js,所以JQuery也属于网页编程语言。 随着计算机行业的迅速发展,出现了各种各…

    2025年3月8日
    200
  • js中隐藏元素用什么方法

    js中隐藏元素可以使用html中的style属性来实现。具体方法如:【style.display=”block”】,表示控件可见;【style.display=”none”】,表示控件不可见。…

    2025年3月8日
    200
  • 学js需要什么基础?

    javascript一种直译式脚本语言,简称js,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为javascript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在html网页上使用,用来给html网页增加…

    2025年3月8日
    200
  • json解析是什么?

    json是一种传递对象的语法,对象可以是name/value对,数组和其他对象。json(javascript object notation) 是一种轻量级的数据交换格式。 它基于JavaScript(Standard ECMA-262 …

    2025年3月8日
    200
  • js全称是什么?

    js全称为javascript,是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。 javascript的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标准通用…

    2025年3月8日
    200
  • js闭包是什么

    闭包就是能够读取其他函数内部变量的函数。由于在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。 JavaScript闭包 在J…

    2025年3月8日
    200
  • web前端js是什么

    js是JavaScript的简称,是一种直译式脚本语言,它是web前端中不可缺少的一部分,它用于增强HTML页面,通常可以嵌入HTML代码中,JavaScript以交互式和动态的方式呈现网页,这允许页面对事件做出反应,展示特殊效果。 JS是…

    2025年3月8日
    200
  • js null是什么类型

    null类型是第二个只有一个值的数据类型,这个特殊的值是null,从逻辑角度来看,null值表示一个空对象指针,而这也正是使用typeof操作符检测null值会返回“object”的原因。 如下面的例子所示: var car =null;a…

    2025年3月8日
    200

发表回复

登录后才能评论