KMP算法

2025年6月15日 8点热度 0人点赞 0条评论

KMP算法(Knuth-Morris-Pratt)是一种高效的字符串匹配算法,它通过避免回溯主串指针来实现O(n+m)的时间复杂度。要深入理解KMP算法,需要掌握其核心思想——​​部分匹配表(Pi数组)​​和​​匹配过程中的智能回退机制​​。

1. 核心思想:利用已匹配信息避免重复比较

传统暴力匹配算法在每次失败时需要回溯主串指针,导致大量重复比较。KMP算法通过预处理模式串,构建一个"部分匹配表"(Pi数组),利用这个表在匹配失败时只回退模式串指针,主串指针永不回溯。

2. 部分匹配表(Pi数组)

Pi数组存储了模式串每个位置的最长公共前缀后缀长度(Longest Proper Prefix which is also Suffix)。

定义:

pi[i] = 子串needle[0..i]中,既是真前缀(不包含自身)又是真后缀(不包含自身)的最长子串长度。

示例(模式串"ababc"):

示例(模式串"ababc"):

子串真前缀真后缀公共部分pi[i]
a0
ab[a][b]0
aba[a, ab][ba, a]"a"1
abab[a,ab,aba][bab,ab,b]"ab"2
ababc[a,ab,aba,abab][babc,abc,bc,c]0

Pi数组结果:[0, 0, 1, 2, 0]

Pi数组构建过程:

vector<int> pi(m, 0);  // 初始化全0
for (int i = 1, j = 0; i < m; i++) {
    while (j > 0 && needle[i] != needle[j])
        j = pi[j-1];  // 回退到前一个匹配位置
    
    if (needle[i] == needle[j]) 
        j++;  // 匹配时扩展公共长度
    
    pi[i] = j;  // 记录当前位置的LPS
}

3. 匹配过程:智能回退机制

当匹配失败时,利用Pi值决定模式串指针回退的位置,主串指针不回溯。

for (int i = 0, j = 0; i < n; i++) {
    while (j > 0 && haystack[i] != needle[j])
        j = pi[j-1];  // 关键:回退模式串指针
    
    if (haystack[i] == needle[j]) 
        j++;
    
    if (j == m)  // 完全匹配
        return i - m + 1;
}

匹配示例(主串:"abababc",模式串:"ababc"):

步骤1: [a]bababc ←→ [a]babc  匹配,j=1
步骤2: a[b]ababc ←→ a[b]abc  匹配,j=2
步骤3: ab[a]babc ←→ ab[a]bc  匹配,j=3
步骤4: aba[b]abc ←→ aba[b]c  匹配,j=4
步骤5: abab[a]bc ←→ abab[c]  不匹配 → 回退 j = pi[3] = 2
         ▲(主串指针保持不动)
         ab[a]bc  ←→ 回退后重新比较
步骤6: abab[a]bc ←→ ab[a]bc  匹配,j=3
步骤7: ababa[b]c ←→ aba[b]c  不匹配 → 回退 j = pi[2] = 1
步骤8: ababa[b]c ←→ a[b]c    不匹配 → 回退 j = pi[0] = 0
步骤9: ababa[b]c ←→ [a]bc    不匹配 → i继续前进
...

4. KMP算法的本质理解

  1. ​空间换时间​​:预处理Pi数组(O(m)时间)
  2. ​后缀即前缀​​:利用模式串自身重复特性(如"abab"中"ab"即是前缀也是后缀)
  3. ​回退即跳跃​​:失败时不重头开始,而是跳到"已匹配部分"的后缀起点
  4. ​永不回溯主串​​:主串指针单向移动,时间复杂度稳定O(n+m)

5. 实际应用场景

  • 文本编辑器中的查找功能
  • DNA序列匹配(长模式串匹配)
  • 网络数据包内容分析
  • 编译器词法分析

理解KMP的关键在于将​​模式串的自我匹配​​(构建Pi表)和​​主串的智能匹配​​(回退机制)看作同一思想的两种应用。掌握Pi数组的物理含义和回退时的"最长公共前后缀"原则,就能真正理解KMP的精髓。

MuWinds

这个人很懒,什么都没留下

文章评论