KMP 算法

这里写目录标题

KMP数组计算方式

c 复制代码
#include <iostream>
#include <vector>
using namespace std;

void computeNext(const string& pattern) {
    int n = pattern.size();
    vector<int> next(n, 0); // 初始化 next 数组
    int j = 0; // 前缀指针

    cout << "Step-by-step explanation:" << endl;

    for (int i = 1; i < n; ++i) {
        // 处理回退
        while (j > 0 && pattern[i] != pattern[j]) {
            cout << "Mismatch: i=" << i << ", j=" << j
                 << " -> backtrack j=next[" << (j - 1) << "]=" << next[j - 1] << endl;
            j = next[j - 1];
        }

        // 匹配成功
        if (pattern[i] == pattern[j]) {
            j++;
        }
        next[i] = j;

        // 输出当前步骤
        cout << "i=" << i << ", j=" << j << ", next[" << i << "]=" << next[i] << endl;
    }

    // 输出完整的 next 数组
    cout << "Final next array: ";
    for (int x : next) {
        cout << x << " ";
    }
    cout << endl;
}

int main() {
    string pattern = "ababaca";
    computeNext(pattern);
    return 0;
}

我们一步步详细讲解如何求 next 数组,以最直观的方式拆解过程。


问题描述

给定模式串:ababaca

目标:计算出每个位置 inext[i],即从开头到位置 i 子串的最长相同前后缀的长度。


初始准备

  1. 模式串长度: 模式串为 ababaca,长度为 7。
  2. next 数组: 初始化为 [0, 0, 0, 0, 0, 0, 0]
    • next[0] = 0,因为第一个字符没有前后缀。

我们从模式串的第 1 个字符开始(i=1),逐步推导 next 数组。


逐步推导过程

Step 1: i = 1,子串为 ab
  • 比较 pattern[1] (b) 和 pattern[0] (a)。
  • 不相同,所以没有前后缀,next[1] = 0

当前 next 数组:[0, 0, 0, 0, 0, 0, 0]


Step 2: i = 2,子串为 aba
  • 比较 pattern[2] (a) 和 pattern[0] (a)。
  • 相同,说明前后缀延续,最长前后缀长度为 1。
  • 更新:next[2] = 1

当前 next 数组:[0, 0, 1, 0, 0, 0, 0]


Step 3: i = 3,子串为 abab
  • 比较 pattern[3] (b) 和 pattern[1] (b)。
  • 相同,说明前后缀延续,最长前后缀长度为 2。
  • 更新:next[3] = 2

当前 next 数组:[0, 0, 1, 2, 0, 0, 0]


Step 4: i = 4,子串为 ababa
  • 比较 pattern[4] (a) 和 pattern[2] (a)。
  • 相同,说明前后缀延续,最长前后缀长度为 3。
  • 更新:next[4] = 3

当前 next 数组:[0, 0, 1, 2, 3, 0, 0]


Step 5: i = 5,子串为 ababac
  • 比较 pattern[5] (c) 和 pattern[3] (b)。
  • 不相同,需要回退。
  • 回退到 next[j-1] = 1,再比较 pattern[5]pattern[1] (b)。
  • 仍然不相同,再回退到 next[j-1] = 0, while (j > 0 && pattern[i] != pattern[j]) 结束。
  • pattern[5]pattern[0] (a)仍然不相同,最终 j = 0,此时没有前后缀。
  • 更新:next[5] = 0

当前 next 数组:[0, 0, 1, 2, 3, 0, 0]


Step 6: i = 6,子串为 ababaca
  • 比较 pattern[6] (a) 和 pattern[0] (a)。
  • 相同,最长前后缀长度为 1。
  • 更新:next[6] = 1

当前 next 数组:[0, 0, 1, 2, 3, 0, 1]


完整结果

最终 next 数组为:[0, 0, 1, 2, 3, 0, 1]


关键步骤总结

  1. 匹配成功时: 如果 pattern[i] == pattern[j],前后缀匹配,更新 next[i] = j + 1,并继续处理下一个字符。
  2. 匹配失败时:
    • 回退到前一个可能的前缀位置(j = next[j-1]),直到找到可以匹配的前缀,或者回退到 j = 0
  3. 初始化: next[0] = 0,第一个字符没有前后缀。

代码一步步实现

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

void computeNext(const string& pattern) {
    int n = pattern.size();
    vector<int> next(n, 0); // 初始化 next 数组
    int j = 0; // 前缀指针

    cout << "Step-by-step explanation:" << endl;

    for (int i = 1; i < n; ++i) {
        // 处理回退
        while (j > 0 && pattern[i] != pattern[j]) {
            cout << "Mismatch: i=" << i << ", j=" << j
                 << " -> backtrack j=next[" << (j - 1) << "]=" << next[j - 1] << endl;
            j = next[j - 1];
        }

        // 匹配成功
        if (pattern[i] == pattern[j]) {
            j++;
        }
        next[i] = j;

        // 输出当前步骤
        cout << "i=" << i << ", j=" << j << ", next[" << i << "]=" << next[i] << endl;
    }

    // 输出完整的 next 数组
    cout << "Final next array: ";
    for (int x : next) {
        cout << x << " ";
    }
    cout << endl;
}

int main() {
    string pattern = "ababaca";
    computeNext(pattern);
    return 0;
}

运行结果展示

对于模式串 ababaca,逐步打印过程如下:

Step-by-step explanation:
i=1, j=0, next[1]=0
i=2, j=1, next[2]=1
i=3, j=2, next[3]=2
i=4, j=3, next[4]=3
Mismatch: i=5, j=3 -> backtrack j=next[2]=1
Mismatch: i=5, j=1 -> backtrack j=next[0]=0
i=5, j=0, next[5]=0
i=6, j=1, next[6]=1
Final next array: 0 0 1 2 3 0 1

总结

这种逐步回退+匹配的方法能有效构建 next 数组,时间复杂度为 (O(n)),非常高效!

j = next[j - 1] 是 KMP 算法求解 next 数组的核心逻辑,用于在字符不匹配时 回退 前缀指针 j,快速找到新的匹配位置。


匹配失败时 核心思路

  1. next[j-1] 存储的是从 pattern[0..j-1] 这段子串的最长相同前后缀的长度。
  2. 回退到 next[j-1],表示将 j 移动到这个较短前缀的末尾,从该位置重新尝试匹配。

直观解释

假设当前已经匹配了 j 个字符(即 pattern[0..j-1]),但下一个字符 pattern[i] 不匹配 pattern[j]

情况 1:暴力回退
  • 如果不用 next 数组,匹配失败时只能暴力将 j 回到 0,重新开始匹配,效率较低。
情况 2:利用 next 回退
  • next[j-1] 告诉我们:模式串 pattern[0..j-1] 中,最长相同前后缀的长度是 next[j-1]
  • 因此,直接将 j 移动到 next[j-1],相当于跳过一部分前缀字符,避免重复比较。
  • pattern[next[j-1]] 开始尝试匹配,能快速继续匹配,而不需要回退到开头。

举例分析

模式串:ababaca

我们计算 next 数组时,假设当前:

  • ( i = 5 ),即正在处理 pattern[5] (c)。
  • ( j = 3 ),表示之前已经匹配了 3 个字符(前缀 aba)。

此时,pattern[5] != pattern[3],匹配失败。


如何回退?

  1. j = 3 时,之前匹配的子串是 aba,其最长相同前后缀长度为 next[3-1] = next[2] = 1
    • 意味着,前缀 a 和后缀 a 是匹配的。
  2. 所以,直接将 j 移动到位置 1next[2] 的值),尝试从更短的前缀 a 开始重新匹配 pattern[i] = c

结果:

  1. pattern[i] 仍然不匹配(pattern[5] != pattern[1]),继续回退 j = next[j-1] = next[0] = 0
  2. 最终回退到 j = 0,表示无法找到更短的前缀可以匹配。

相关推荐
MiyamiKK573 分钟前
leetcode_字符串 409. 最长回文串
数据结构·算法·leetcode
半盏茶香23 分钟前
扬帆数据结构算法之雅舟航程,漫步C++幽谷——LeetCode刷题之移除链表元素、反转链表、找中间节点、合并有序链表、链表的回文结构
数据结构·c++·算法
CodeJourney.43 分钟前
小型分布式发电项目优化设计方案
算法
带多刺的玫瑰1 小时前
Leecode刷题C语言之从栈中取出K个硬币的最大面积和
数据结构·算法·图论
Cando学算法1 小时前
Codeforces Round 1000 (Div. 2)(前三题)
数据结构·c++·算法
薯条不要番茄酱1 小时前
【动态规划】落花人独立,微雨燕双飞 - 8. 01背包问题
算法·动态规划
小林熬夜学编程1 小时前
【Python】第三弹---编程基础进阶:掌握输入输出与运算符的全面指南
开发语言·python·算法
字节高级特工1 小时前
【优选算法】5----有效三角形个数
c++·算法
小孟Java攻城狮7 小时前
leetcode-不同路径问题
算法·leetcode·职场和发展
查理零世7 小时前
算法竞赛之差分进阶——等差数列差分 python
python·算法·差分