KMP算法原理详解

KMP算法原理详解

KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,核心思想是通过预处理模式串构建next数组(部分匹配表),在匹配失败时利用已匹配信息跳过无效比较,将时间复杂度优化至O(n+m)(n为文本串长度,m为模式串长度)。


一、核心原理图解

1. 朴素匹配 vs KMP匹配

朴素匹配在失配时回溯文本串指针,导致重复比较(时间复杂度O(n×m)):
在这里插入图片描述

KMP匹配在失配时保持文本串指针不回溯,仅移动模式串:
在这里插入图片描述

2. next数组原理

next[j]表示模式串位置j之前子串的最长公共前后缀长度

  • 前缀:包含首字符但不包含尾字符的子串
  • 后缀:包含尾字符但不包含首字符的子串

示例(模式串"ABABA"):

子串前缀后缀最长公共前后缀next值
A[][]无(长度0)0
AB[A][B]0
ABA[A, AB][BA, A]“A”(长度1)1
ABAB[A, AB, ABA][BAB, AB, B]“AB”(长度2)2
ABABA[A, AB, ABA, ABAB][BABA, ABA, BA, A]“ABA”(长度3)3

next数组计算流程(动态规划):
在这里插入图片描述


二、匹配过程图解(以文本串"ABABCABAC"和模式串"ABABA"为例)

1. 初始化
文本串:A B A B C A B A C
模式串:A B A B A
next:  -1 0 0 1 2
        ↑j=0
        ↑i=0
2. 第一轮匹配(i=4时失配)
文本串:A B A B C ...
模式串:A B A B A
              ↑j=4(失配)
  • 已匹配部分:“ABAB”
  • 最长公共前后缀:“AB”(长度2)
  • 移动位数:4 - next[4] = 4 - 2 = 2
  • 新模式串位置
文本串:A B A B C ...
模式串:    A B A B A
              ↑j=2(next[4]=2)
3. 第二轮匹配(i=4继续比较)
文本串:A B A B C ...
模式串:    A B A B A
              ↑j=2 → 匹配继续
最终匹配成功:i=8, j=5 → 返回8-5=3

三、算法实现解析

1. next数组计算优化

通过动态规划递推求解,避免重复比较:

  • P[i] == P[j],则next[i+1] = j+1
  • 若不等,则j = next[j]回溯:
while (i < sub_len - 1) {
    if (j == -1 || sub[i] == sub[j]) {
        j++; i++;
        if (sub[i] == sub[j]) next[i] = next[j];  // 优化:避免重复匹配
        else next[i] = j;
    } else {
        j = next[j];  // 回溯
    }
}
2. 匹配过程核心逻辑

在这里插入图片描述


四、应用场景

KMP算法在以下领域广泛应用:

  1. 文本编辑器:快速查找/替换(如Notepad++、VS Code)
  2. 生物信息学:DNA/RNA序列匹配(如基因片段比对)
  3. 数据压缩:LZ系列算法中的字典更新优化
  4. 网络安全:入侵检测系统中的特征码匹配
  5. 数据库系统:加速文本索引和查询

五、完整源代码

/**
 * KMP字符串匹配算法实现
 * 功能:在文本串src中查找模式串sub首次出现的位置
 * 时间复杂度:O(n + m)
 */
template <typename T>
int FindIndexOf(int* next, T* src, int src_len, T* sub, int sub_len) noexcept {
    // 计算next数组的Lambda函数
    static constexpr auto FindNextOf = 
        [](int* next, T* sub, int sub_len) noexcept {
            int l = sub_len - 1;
            int i = 0;
            int j = -1;     
            next[0] = -1;
            while (i < l) {
                if (j == -1 || sub[i] == sub[j]) {
                    j++;
                    i++;
                    // 优化:避免重复匹配
                    if (sub[i] == sub[j]) {
                        next[i] = next[j];
                    }
                    else {
                        next[i] = j;
                    }
                }
                else {
                    j = next[j];
                }
            }
        };

    int i = 0;
    int j = 0;
    FindNextOf(next, sub, sub_len);  // 预处理next数组

    // KMP匹配主循环
    while (i < src_len && j < sub_len) {
        if (j == -1 || src[i] == sub[j]) {
            i++;
            j++;
        }
        else {
            j = next[j];  // 失配时跳转到next[j]
        }
    }

    // 返回匹配结果
    if (j >= sub_len) {
        return i - sub_len;  // 匹配成功
    }
    else {
        return -1;  // 未匹配
    }
}

关键改进说明

  1. next数组计算中增加sub[i] == sub[j]判断,避免重复匹配(即nextval优化)
  2. 模板化设计支持任意数据类型(如charwchar_t等)
  3. 无异常保证(noexcept)适用于高性能场景

六、参考图示

1. next数组计算过程(模式串"ABABCAB"):

在这里插入图片描述

步骤解析

  1. 初始化next[0] = -1(无前缀),next[1] = 0(单字符无真前后缀)。
  2. i=2P[2]='A' 匹配 P[0]='A'next[2]=1(最长公共前后缀长度=1)。
  3. i=3P[3]='B' 匹配 P[1]='B'next[3]=2(公共前后缀 "AB")。
  4. i=4P[4]='C' 不匹配 P[2]='A' → 回溯 k=next[2]=1 → 仍不匹配 P[1]='B' → 回溯至 k=0 → 不匹配 P[0]='A'next[4]=0
  5. i=5P[5]='A' 匹配 P[0]='A'next[5]=1
  6. i=6P[6]='B' 匹配 P[1]='B'next[6]=2(公共前后缀 "AB")。

最终 next 数组[-1, 0, 1, 2, 0, 1, 2]

2. 完整匹配流程图

在这里插入图片描述

流程说明
3. 初始化:文本指针 i=0,模式指针 j=0
4. 字符匹配

  • S[i] = P[j],则 i++, j++
  • S[i] ≠ P[j]
    • j=0,则 i++(文本指针后移)。
    • 否则 j = next[j](模式指针回溯至最长公共前后缀位置)。
  1. 匹配成功:当 j = len(P) 时,返回匹配起始位置 i - j
  2. 跳转优化:失配时通过 next 数组跳过已匹配前缀,避免文本指针回溯。
3. 关键点总结
  • Next 数组作用:记录模式串的自匹配信息(最长公共前后缀长度),匹配失败时通过 j=next[j] 跳转,使 i 不回溯,将时间复杂度优化至 O(n+m)O(n+m)O(n+m)
  • 匹配流程优势:相比暴力匹配(O(n×m)O(n \times m)O(n×m)),KMP 的文本指针 i 永不回溯,仅模式指针 j 根据 next 数组调整位置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值