数据结构——22. KMP算法和Trie树

第一部分:KMP 字符串匹配算法

KMP 算法是一种高效的字符串匹配算法,它的核心在于避免了在文本串中不必要的回溯,从而将时间复杂度从暴力匹配的 O(m×n)O(m \times n)O(m×n) 优化到 O(m+n)O(m+n)O(m+n)

1. 核心问题:模式匹配

  • 目标串 (Target String):一个较长的字符串,我们通常用 S 表示,长度为 n
  • 模式串 (Pattern String):一个较短的,我们希望在目标串中查找的字符串,通常用 P 表示,长度为 m
  • 目标:在 S 中找到 P 首次出现的位置。

2. 朴素(暴力)匹配算法及其弊端

朴素算法的思路很简单:

  1. S 的第一个字符与 P 的第一个字符对齐,然后逐一比较。
  2. 如果所有字符都匹配成功,则返回起始位置。
  3. 如果中途遇到不匹配的字符,则将 P 向后移动一位,再次从 P 的第一个字符开始与 S 的新位置对齐比较。
  4. 重复此过程,直到找到匹配或 S 遍历完毕。

弊端:效率低下。在最坏情况下(例如 S=“aaaaaaaaab”, P=“aaab”),每次失配后,S 的指针 i 都要回溯,P 的指针 j 都要归零,导致大量重复比较。

3. KMP 算法的核心思想

KMP算法的巧妙之处在于,当 S[i]P[j] 发生不匹配时,它不会简单地将 i 回溯,j 归零。而是利用已经匹配过的信息,让模式串 P 尽可能地向右滑动多位,并让 j 跳转到一个新的位置继续比较。

这个“跳转到哪里”的信息,就存储在一个预先计算好的辅助数组中,这个数组通常被称为 next 数组 (在你的课件中也被称为“失败函数” f(j) 的计算结果)。

4. 关键:next 数组 (前缀函数)

next 数组是 KMP 算法的精髓,它完全根据模式串 P 本身的结构来计算。

  • 定义next[j] 的值表示 P 的子串 P[0...j-1] 中,最长相等前后缀的长度。

    • 前缀:指除最后一个字符以外,字符串的所有头部子串。
    • 后缀:指除第一个字符以外,字符串的所有尾部子串。
    • 最长相等前后缀:例如,对于字符串 “ababa”,
      • 前缀有: “a”, “ab”, “aba”, “abab”
      • 后缀有: “b”, “ba”, “aba”, “baba”
      • 相等的前后缀有 “a” 和 “aba”。最长的那个是 “aba”,长度为 3。
  • 作用:当 S[i]P[j] 不匹配时,我们不必从头再来。我们知道 S[i-j ... i-1] 已经和 P[0 ... j-1] 匹配成功了。根据 next 数组,我们知道 P[0 ... j-1] 的一个长度为 k = next[j] 的前缀 P[0 ... k-1] 和它的一个后缀 P[j-k ... j-1] 是相等的。因此,我们可以直接将模式串向右滑动,让这个前缀 P[0 ... k-1] 对齐到刚刚匹配上的后缀位置,然后从 P[k] 继续和 S[i] 进行比较。这个过程指针 i 完全不需要回溯。

  • 计算示例:以模式串 P = "ababc" 为例计算 next 数组。

    • j=0: P[0...-1] (空串),没有前后缀,next[0] = 0。(在某些实现中,为了方便设为-1,我们这里统一设为0)
    • j=1: P[0...0] = “a”,最长相等前后缀长度为 0,next[1] = 0
    • j=2: P[0...1] = “ab”,最长相等前后缀长度为 0,next[2] = 0
    • j=3: P[0...2] = “aba”,最长相等前后缀为 “a”,长度为 1,next[3] = 1
    • j=4: P[0...3] = “abab”,最长相等前后缀为 “ab”,长度为 2,next[4] = 2
    • j=5: P[0...4] = “ababc”,最长相等前后缀长度为 0,next[5] = 0
    • 所以,P = "ababc"next 数组为 [0, 0, 0, 1, 2]

5. KMP 算法实现 (机试必备)

a. next 数组的计算

// C++ 实现
#include <vector>
#include <string>

void computeNext(const std::string& p, std::vector<int>& next) {
   
   
    int m = p.length();
    next.resize(m, 0);
    for (int i = 1, len = 0; i < m; ) {
   
   
        if (p[i] == p[len]) {
   
   
            len++;
            next[i] = len;
            i++;
        } else {
   
   
            if (len != 0) {
   
   
                len = next[len - 1];
            } else {
   
   <
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱看烟花的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值