Knuth-Morris-Pratt (KMP) 字符串匹配算法

如果您喜欢这篇文章,欢迎访问我的个人博客查看原文并参与讨论与互动 【原文链接

字符串匹配算法

字符串匹配算法旨在找到字符串 s 中与搜索词 w 匹配的起始索引 m。[ ^wiki ] 这种过程是文本处理和计算机科学应用中的基础,例如文本编辑和数据检索。

例子

  • s = "ABCABABCABDA"
  • w = "ABCABD"

暴力解法

暴力方法采用了一个简单的方案,使用两个指针:一个遍历主字符串 s,另一个用于检查目标单词 w。对于 s 中的每个字符,它会比较接下来的字符与 w 的对应字符,直到比较长度达到 w 的长度。如果出现不匹配,s 中的指针将移动到下一个字符,并重新开始比较。

在这里插入图片描述

然而,这种方法的时间复杂度为 O(mn),其中 m = len(s)n = len(w)。当不匹配经常发生在 w 的末尾并且 w 出现在 s 中较晚时,这种方法的效率极其低下。例如,考虑 s = "000000000000000000001"w = "00000001"。在这种情况下,几乎每次尝试都将处理 w 的几乎整个长度,然后才遇到不匹配,导致搜索过程中大量的冗余。

在这里插入图片描述

KMP 的优势

Knuth-Morris-Pratt (KMP) 算法通过使用 前缀表(又称 “部分匹配” 表1next 来提高效率。该表允许算法跳过已与 w 部分匹配的 s 的部分,从而避免重新评估根据前缀表将匹配的字符。

在这里插入图片描述

KMP 工作原理

前缀和后缀

  • 前缀表
    • 这个表是由搜索词 w 构造的,记录了每个位置 i 结束的子字符串的最长前缀与后缀匹配的长度。
    • 前缀
      • 一个字符串的前缀是任何以字符串开头的子串,但不包括最后一个字符。这些是字符串可能的前导段。
    • 后缀
      • 一个字符串的后缀是任何以字符串末尾结束的子串,但不包括第一个字符。这些是字符串可能的末尾段2

在这里插入图片描述

对于每个 next[i],它记录了 w 的最长前缀与 w[i] 之前结束的后缀匹配的长度。前缀表 next 允许我们跳到 w[next[i]],有效地跳过之前已保证匹配的字符,避免了冗余检查。

预处理:创建前缀表

这个预处理步骤包括以下三个主要阶段:

  1. 初始化
  2. 处理不匹配
  3. 处理匹配

1. 初始化

首先,创建一个与模式 w 长度相同的数组 next,并将所有条目初始化为 0

get_next(w, next):
	next[0] = 0

然后,初始化指针 i = 1j = 0,其中:

  • i 是后缀的结尾,也是下一个将更新的 next 元素。
  • j 是前缀的结尾,也表示最长匹配的前缀和后缀的长度。

2. 处理不匹配

当前缀的最后一个元素 s[j] 与后缀 w[i] 不匹配时,我们更新指针 j,寻找下一个可能的前缀长度,然后将 j 前移一步,查看该位置的 next 值。

while j > 0 and w[i] != w[j]:
        j = next[j - 1]

在某些情况下,即使回退到先前的前缀长度(j = next[j-1]),j 的新位置仍然不匹配当前字符 w[i]。因此,while 循环(while j > 0 and w[i] != w[j])确保算法持续调整被考虑的前缀长度,直到找到匹配的前缀或耗尽所有可能的前缀。

3. 处理匹配

当找到 w[i]w[j] 的匹配时,表示前缀长度为 j,可以通过一个字符延伸(w[i])。

if w[i] == w[j]:
        j += 1

完整代码

get_next(w, next):
	next[0] = 0
  for i in range(1, len(w)):
      while j > 0 and w[i] != w[j]:
          j = next[j - 1]
      if w[i] == w[j]:
          j += 1
      next[i] = j

搜索:对齐到下一个潜在匹配

前缀表的使用取决于具体的问题。以下列出了一些实现 KMP 的示例问题:

力扣问题说明
28 在字符串中找到第一个匹配的位置专注于在长文本中找到第一个匹配,直接应用 KMP 快速搜索能力。
459 重复的子字符串模式使用前缀表确定字符串是否可以由重复的子字符串构成,展示了 KMP 预处理的灵活应用。

预处理的复杂度

由于构建前缀表的预处理阶段和随后的搜索阶段分别以 O(n)O(m) 时间运行,其中 n 是模式 w 的长度,m 是文本 s 的长度,KMP 算法的总复杂度为 O(n+m)

如果您喜欢这篇文章,欢迎访问我的个人博客查看原文并参与讨论与互动 【原文链接

参考


  1. Wikipedia - Knuth–Morris–Pratt algorithm:https://en.wikipedia.org/wiki/Knuth–Morris–Pratt_algorithm. ↩︎

  2. 代码随想录: https://programmercarl.com/0028.实现strStr.html#思路 ↩︎

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
串匹配是指在一个文本串中查找另一个模式串的过程。常用的串匹配算法有Naïve算法、Rabin-Karp算法Knuth-Morris-Pratt算法。 1. Naïve算法 Naïve算法是最简单的串匹配算法,也称为暴力匹配算法。它的思路是从文本串的第一个字符开始,依次比较文本串中的每个字符是否与模式串中的字符相等。若不相等,则继续向后比较;若相等,则比较下一个字符,直到找到完全匹配的子串或文本串被匹配完为止。 Naïve算法的时间复杂度是O(mn),其中m和n分别是模式串和文本串的长度。当模式串和文本串长度相等时,最坏情况下时间复杂度达到O(n^2)。 2. Rabin-Karp算法 Rabin-Karp算法是一种基于哈希值的串匹配算法。它的思路是先将模式串和文本串都转换为哈希值,然后比较它们的哈希值是否相等。如果哈希值相等,则再逐个比较模式串和文本串中的字符是否相等。这种方法可以有效地减少比较次数,提高匹配效率。 Rabin-Karp算法的时间复杂度是O(m+n),其中m和n分别是模式串和文本串的长度。但是,由于哈希函数的不完全性和哈希冲突的存在,Rabin-Karp算法在某些情况下可能会出现误判。 3. Knuth-Morris-Pratt算法 Knuth-Morris-Pratt算法是一种基于前缀函数的串匹配算法。它的思路是先计算出模式串的前缀函数,然后利用前缀函数的信息来跳过已经匹配过的部分,减少比较次数。 具体来说,KMP算法在匹配过程中维护一个指针i和一个指针j,其中i指向文本串中当前匹配的位置,j指向模式串中当前匹配的位置。如果当前字符匹配成功,则i和j同时向后移动一位;如果匹配失败,则通过前缀函数计算出j需要跳转到的位置,使得前j-1个字符与文本串中的对应字符已经匹配成功,然后将j指向这个位置,i不变,继续比较下一个字符。 KMP算法的时间复杂度是O(m+n),其中m和n分别是模式串和文本串的长度。由于利用了前缀函数的信息,KMP算法可以在最坏情况下达到O(n)的时间复杂度,比Naïve算法和Rabin-Karp算法更加高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值