KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,其核心思想是**避免主串指针回溯**,通过预处理模式串构造 `next` 数组

KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,其核心思想是避免主串指针回溯,通过预处理模式串构造 next 数组,使得在发生字符不匹配时,能够快速调整模式串的匹配位置。以下是针对你提供内容的完整解析与代码实现:

def compute_next(pattern):
    """计算模式串的 next 数组"""
    m = len(pattern)
    next_arr = [-1] * m  # 初始化 next 数组,第一个元素为 -1
    j = -1  # 指向前缀末尾(初始为 -1)
    for i in range(1, m):  # i 指向后缀末尾
        while j != -1 and pattern[i] != pattern[j + 1]:
            j = next_arr[j]
        if pattern[i] == pattern[j + 1]:
            j += 1
        next_arr[i] = j
    return next_arr

def kmp_search(text, pattern):
    """KMP 算法主函数:返回模式串在主串中首次出现的起始下标,未找到返回 -1"""
    if not pattern:
        return 0
    n, m = len(text), len(pattern)
    next_arr = compute_next(pattern)
    j = -1  # 模式串匹配指针(当前已匹配到的位置)

    for i in range(n):  # 遍历主串
        while j != -1 and text[i] != pattern[j + 1]:
            j = next_arr[j]  # 失配时利用 next 跳转
        if text[i] == pattern[j + 1]:
            j += 1
        if j == m - 1:  # 完全匹配
            return i - m + 1  # 返回起始位置
    return -1  # 匹配失败

# 示例使用
text = "abacbababbcbc"
pattern = "abababb"
next_table = compute_next(pattern)
print("Next数组:", next_table)  # 输出: [-1, 0, 0, 1, 2, 3, 4]
pos = kmp_search(text, pattern)
print(f"模式串 '{pattern}' 在主串中的起始位置: {pos}")

解析说明:

  • next 数组含义next[j] 表示模式串前 j+1 个字符中最长相等前后缀的长度减一(即回退位置)。例如 "abababb"next = [-1,0,0,1,2,3,4]
  • 匹配过程:当 text[i] != pattern[j] 时,令 j = next[j],无需移动 i,从而保证时间复杂度为 O(n+m)。
  • 效率优势:相比朴素算法每次失配都需回溯主串指针,KMP 实现了主串只遍历一次。
    手动推导 KMP 算法中模式串的 next 数组,核心是对每个位置 j,求其子串 pattern[0…j] 的最长相等真前缀与真后缀的长度,并将该长度减一作为 next[j](即回退到的位置下标)。我们以模式串 "abaabc" 为例,逐步推导其 next 数组。

✅ 模式串:"abaabc"

j(下标)012345
字符abaabc

我们将逐位计算 next[j]


🔹 j = 0
  • 子串:"a"
  • 无真前缀和真后缀 → 最长相等前后缀长度为 0
  • next[0] = -1 (规定第一个值为 -1)

✅ next[0] = -1


🔹 j = 1
  • 子串:"ab"
  • 前缀集合:{“a”}
  • 后缀集合:{“b”}
  • 相等前后缀:空 → 长度为 0
  • next[1] = 0

✅ next[1] = 0


🔹 j = 2
  • 子串:"aba"
  • 前缀:{“a”, “ab”}
  • 后缀:{“a”, “ba”}
  • 共同部分:“a” → 长度为 1
  • 所以最长相等前后缀长度为 1 → 下标为 0
  • next[2] = 0(因为要记录的是前缀最后一个字符的下标)

✅ next[2] = 0


🔹 j = 3
  • 子串:"abaa"
  • 前缀:{“a”, “ab”, “aba”}
  • 后缀:{“a”, “aa”, “baa”}
  • 共同部分:“a” → 长度为 1 → 对应下标 0
  • next[3] = 0

✅ next[3] = 0


🔹 j = 4
  • 子串:"abaab"
  • 前缀:{“a”, “ab”, “aba”, “abaa”}
  • 后缀:{“b”, “ab”, “aab”, “baab”}
  • 共同部分:“ab” → 长度为 2 → 对应下标 1
  • next[4] = 1

✅ next[4] = 1


🔹 j = 5
  • 子串:"abaabc"
  • 前缀:{“a”, “ab”, “aba”, “abaa”, “abaab”}
  • 后缀:{“c”, “bc”, “abc”, “aabc”, “baabc”}
  • 没有公共非空相等前后缀?检查:
    • “a” vs “c” → 不等
    • “ab” vs “bc” → 不等
    • 仅可能匹配长度为 0
  • 实际上唯一可能的是长度为 0
  • next[5] = 0

✅ next[5] = 0


📊 最终结果:

j012345
字符abaabc
next[j]-100010

✅ 总结推导方法(手动步骤):

  1. next[0] = -1 固定。
  2. 对于每个后续位置 j ≥ 1:
    • 考察子串 pattern[0..j]
    • 找出它的所有真前缀真后缀
    • 找出其中最长的相等前后缀
    • 记录这个最长前缀的最后一个字符的下标 → 即为 next[j]
  3. 可用递推思想辅助:若已知前面的 next 值,可通过类 KMP 方式模拟构建。

# 验证代码
def compute_next(pattern):
    m = len(pattern)
    next_arr = [-1] * m
    j = -1
    for i in range(1, m):
        while j != -1 and pattern[i] != pattern[j + 1]:
            j = next_arr[j]
        if pattern[i] == pattern[j + 1]:
            j += 1
        next_arr[i] = j
    return next_arr

print(compute_next("abaabc"))  # 输出: [-1, 0, 0, 0, 1, 0] ✔️

在这里插入图片描述

KMP算法一种高效字符串匹配算法,用于在一个文本中查找指定模式串的所有出现位置。在C++中实现KMP算法,你需要准备两个关键部分:模式串的前缀函数计算以及实际的匹配过程。 1. **计算前缀函数**- 创建一个`next`数组,长度等于模式串的长度。`next[i]`表示在发生错误时,模式串应从当前位置`i`跳到的位置,使得匹配能继续进行。 - 通过以下步骤计算`next`数组- 初始化`next[0] = 0`,因为一个空的前缀不可能导致失败。 - 从`i = 1`开始遍历模式串,如果`pattern[i] == pattern[next[i-1]]`,则`next[i] = next[i-1]+1`;否则,尝试从更大的跳跃值开始找到最长的相等子,即`j = next[next[i-1]]`,然后`next[i] = j + 1`,直到找到一个大于0的`j`或者`pattern[i]`和`pattern[j+1]`都不相等为止。 2. **字符串匹配**- 定义两个指针`text_ptr`和`pattern_ptr`,分别指向文本模式串的起始位置。 - 当`text_ptr`和`pattern_ptr`都有效(即都在各自的内)且`text[text_ptr]`等于`pattern[pattern_ptr]`时,同时增加两个指针- 如果不相等,首先检查`pattern_ptr`是否可以用`next[pattern_ptr]`进行跳跃,若可以则移动`pattern_ptr`;若不能,则只移动`text_ptr`,因为它无法找到正确的匹配。 以下是简单的C++代码示例: ```cpp #include <vector> using namespace std; vector<int> getPrefixFunction(const string& pattern) { int len = pattern.size(); vector<int> next(len); for (int i = 1, j = 0; i < len; ++i) { while (j > 0 && pattern[i] != pattern[j]) j = next[j - 1]; if (pattern[i] == pattern[j]) ++j; next[i] = j; } return next; } bool KMPMatch(const string& text, const string& pattern, vector<int>& prefix) { int text_len = text.length(), pattern_len = pattern.length(); int text_ptr = 0, pattern_ptr = 0; while (text_ptr < text_len && pattern_ptr < pattern_len) { if (text[text_ptr] == pattern[pattern_ptr]) text_ptr++, pattern_ptr++; else if (pattern_ptr > 0) pattern_ptr = prefix[pattern_ptr - 1]; else text_ptr++; } return pattern_ptr == pattern_len; } int main() { string text = "ABABCABADCAB"; string pattern = "ABCD"; vector<int> prefix = getPrefixFunction(pattern); cout << "Pattern found in the text? : " << (KMPMatch(text, pattern, prefix) ? "Yes" : "No") << endl; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Bol5261

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

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

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

打赏作者

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

抵扣说明:

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

余额充值