C++ KMP 算法2025年3月17日 | 阅读 10 分钟 在本教程中,我们将通过代码实现来学习 C++ 中的 KMP 算法。其他用于模式匹配的算法包括朴素算法和 Rabin Karp 算法。如果我们比较这些算法,朴素方法和 Rabin Karp 的时间复杂度为 O((n-m)*m);然而,Rabin Karp 通常优于朴素方法。另一方面,KMP 算法的时间复杂度为 O(n),附加空间为 O(m)。在此示例中,模式字符串和文本字符串的长度分别为 m 和 n。 朴素的模式匹配方法,其中 n 是文本长度,m 是模式长度,如所示,运行时间为 O(n.m)。这是因为该算法缺乏对先前匹配字符的记忆。本质上,它反复将一个字符与一个不同的模式字符进行匹配。 KMP 方法(或 Knuth, Morris, 和 Pratt 字符串搜索方法)之所以有效,在于巧妙地利用了先前的比较数据。由于它从不再次比较已经匹配过模式符号的文本符号,因此它可以在 O(n) 时间内找到模式。为了检查模式结构,它使用一个部分匹配表。构建部分匹配表需要 O(m) 时间。因此,KMP 算法的总时间复杂度为 O(m + n)。 编写一个函数 search(char pat[], char txt[]),该函数在给定文本 txt[0... N-1] 和模式 pat[0... M-1] 时,输出 pat[] 在 txt[] 中的所有实例。N > M 是一个合理的假设。 示例输出 在索引 0 处检测到一个模式,然后在索引 9 和索引 12 处检测到模式。 ![]() 模式搜索是计算机科学中的一个重要问题。当我们对数据库、浏览器或记事本/文字文件执行字符串搜索时,模式搜索方法会显示搜索结果。 在上一个帖子中,我们讨论了朴素模式搜索算法。朴素算法的最坏情况复杂度为 O(m(n-m+1))。KMP 算法的最坏情况时间复杂度为 O(n+m)。 使用 KMP (Knuth Morris Pratt) 进行模式搜索当多个匹配字符后跟一个不匹配字符时,朴素模式搜索算法在查找模式时会遇到困难。 例如,
KMP 匹配算法利用模式的退化特性,当相同的子模式在模式中出现不止一次时,将最坏情况复杂度降低到 O(n+m)。 KMP 方法的基本原则是,如果我们看到一个差异(在几次匹配之后),我们就已经知道下一个窗口文本中的一些字符。利用这些知识,我们可以避免匹配我们知道会匹配的字符。 文本 "AAAAABAAABA." 的比较概述。Pat 是 "AAAA" 我们比较第一个文本窗口和 pat。 pat = "AAAA" 和 txt = "AAAAABAAABA" [起始位置] 我们找到了一个匹配。这与朴素字符串匹配类似。 接下来,我们将文本文件的下一个窗口与 pat 进行比较。 文本 "AAAAABAAABA" pat 等于 "AAAA" [模式移位一位] KMP 在这种情况下对朴素算法进行了优化。为了确定当前文本窗口是否与模式匹配,我们只需将模式的第四个 A 与第二个窗口中的第四个字母进行比较。我们省略了前三个字符的匹配,因为我们知道它们会匹配。 需要预处理吗?如前所述,解释提出了一个关键问题:我如何确定要跳过多少个字符?我们预处理模式以确定这一点,并创建一个名为 lps[] 的整数数组,其中包含将要跳过的字符数。 预处理概述为了在匹配时跳过字符,KMP 算法会对模式进行预处理,并创建一个大小为 m(与模式大小相同)的辅助 lps 数组。 最长的匹配前缀也是后缀,由名称 lps 表示。合适的前缀包含一个完全禁止的字符串。例如,“ABC”的前缀包括“”、“A”、“AB”和“ABC”。合适的前缀是“”、“A”和“AB”。字符串有后缀“”、“C”、“BC”和“ABC”。
lps[i] 是 pat[0..i] 的最长有效前缀,也是 pat[0..i] 的后缀。 应该注意的是,lps[i] 也是最长的前缀,它是有效的后缀。我们必须在一个位置恰当地使用它,以确保整个子字符串不会被考虑在内。 lps[] 构建示例 模式 "AAAA" 的 lps[] 值为 [0, 1, 2, 3]。 模式 "ABCDE" 的 lps[] 值为 [0, 0, 0, 0, 0]。 模式 "AABAACAABAA" 的 Lps[] 是 [0, 1, 0, 1, 2, 0, 1, 2, 3, 4, 5]。 模式 "AAACAAAAAC" 的 lps[] 值为 [0, 1, 2, 0, 1, 2, 3, 3, 4, 5]。 模式 "AAABAAA" 的 lps[] 值为 [0, 1, 2, 0, 1, 2, 3]。 预处理算法在此阶段,我们计算 lps[] 中的值。为了做到这一点,我们使用 len 变量来跟踪前一个索引的最长前缀后缀值长度。
预处理(或 lps[] 创建)示例len = 0, i = 0, pat[] = "AAACAAAA"
KMP 算法实现与朴素方法不同,朴素方法是在每次移位时滑动模式一位并比较所有字符,而我们则利用 lps[] 中的值来确定下一步必须匹配哪些字符。目标是避免匹配一个可能匹配的字符。 如何使用 lps[] 选择下一个位置(或确定需要跳过多少个字符)?
上述方法如下所示考虑 txt = "AAAAABAAABA", pat = "AAAA" 如果使用上述 LPS 构建过程,则 lps[] = 0, 1, 2, 3 -> i = 0, j = 0: txt[i] 和 pat[j] 匹配,执行 i++, j++ -> i = 1, j = 1: txt[i] 和 pat[j] 匹配,执行 i++, j++ -> i = 3, j = 3: txt[i] 和 pat j = lps[j-1] = lps[3] = 3,因为 j = M,打印模式已被发现,j 被重置。 与朴素算法相比,我们在这里不匹配此窗口的前三个字符。在上一阶段,lps[j-1] 的值提供了下一个要匹配的字符的索引。
此过程将持续进行,直到文本中有足够的字符可以与模式中的字符进行比较。 正如前面提到的,该策略的应用如下所示 C++ KMP 模式搜索实现输出 Found pattern at index 10 说明 在上面的代码中,我们使用了 C++ 编程语言的 KMP 算法。KMP 算法用于模式匹配。因此,我们从给定的输入中找到了索引 10 处的模式,如输出所示。 时间复杂度 O(N+M),其中 N 是文本长度,M 是需要发现的模式长度。 辅助空间 O(M) 下一主题C++ 中的抽象数据类型 |
Edmonds-Karp 算法是查找流网络中最大流的一种强大而有效的方法。流网络是一个有向图,其中每条边都有一个容量,表示其可承载的最大流量。该算法建立在 Ford-Fulkerson 方法的基础上,但...
11 分钟阅读
本节将讨论 C++ 编程语言中的 const 关键字。const 关键字用于定义在程序执行期间不能更改的常量值。这意味着一旦我们在程序中将变量声明为常量,该变量的值将...
7 分钟阅读
在本教程中,我们将学习如何声明一个返回整数指针数组指针的 C/C++ 函数。第 1 部分:创建一个考虑 int* 参数并生成指向四个整数指针列表的指针的函数。虽然这乍一看可能很困难,...
阅读 3 分钟
在本文中,我们将讨论 C++ 中的 fma() 函数,包括其语法、参数和示例。简介:C 函数 fma() 设计用于执行合并乘法运算,该运算将 (x * y) + z 作为单个合并操作进行计算,从而减少可能发生的舍入误差……
阅读 4 分钟
在本文中,我们将讨论 C++ 中 std::set 和 std::vector 之间的区别。但在讨论差异之前,我们必须了解 C++ 中的 std::set 和 std::vector。什么是 std::vector?vector 是 C++ 中类似动态数组的容器,它可以包含许多元素的...
阅读 2 分钟
C++ 中 new 和 delete 运算符的区别 在 C++ 编程语言中,new 和 delete 运算符主要用于动态内存分配和去分配。它们使我们能够动态地分配和释放内存,这意味着我们可以创建大小的... 对象
阅读 6 分钟
什么是字符串字面量?匿名字符串[1]或字符串字面量是计算机程序源代码中字符串值的字面量。例如 x = "foo",其中 "foo" 是值为 foo 的字符串字面量,现代计算机语言经常使用带引号的系列...
阅读 3 分钟
如果你处理视觉效果,编写游戏需要扎实的编程技能以及对 OpenGL 和 DirectX 等几个 API 的深刻理解。对于 C++ 程序员来说,有几个游戏引擎可以简化这个过程。必需的头文件...
阅读 4 分钟
C++ 编程中的一个关键思想是指针的概念,它使程序员能够有效地处理数据结构和修改内存地址。在众多指针类型中,对象指针尤其重要,因为它们使处理存储的对象更加容易...
11 分钟阅读
计算机程序中的浮点运算通常涉及可能导致不准确性和异常情况的近似值。当执行敏感的数值计算时,这些异常可能导致不希望的程序终止或不正确的输出。C++ 编程语言提供了处理这些浮点异常的机制和用于...
阅读 6 分钟
我们请求您订阅我们的新闻通讯以获取最新更新。
我们提供所有技术(如 Java 教程、Android、Java 框架)的教程和面试问题
G-13, 2nd Floor, Sec-3, Noida, UP, 201301, India