一、KMP算法的核心思想
KMP算法的核心在于避免重复比较。当在主字符串中匹配模式串时,如果某次匹配失败,朴素算法会将模式串回溯到开头,重新从主字符串的下一个位置开始匹配。而KMP算法通过预处理模式串,计算一个部分匹配表(也叫next数组),记录在匹配失败时模式串应该跳转到的位置,从而跳过不必要的比较。
1.1 问题背景
假设我们有:
- 主字符串 S="ABABAC"S = "ABABAC"S="ABABAC"
- 模式串 P="ABA"P = "ABA"P="ABA"
朴素算法会逐一比较:
- 从 S[0]S[0]S[0] 开始,匹配 PPP,发现 S[2]=′B′≠P[2]=′A′S[2] = 'B' \neq P[2] = 'A'S[2]=′B′=P[2]=′A′,匹配失败。
- 然后从 S[1]S[1]S[1] 开始重新匹配,重复检查已经知道的部分。
KMP算法的优化在于:当匹配失败时,利用模式串 PPP 的信息,决定模式串的指针应该回退到哪里,而不是简单地从头开始。
1.2 部分匹配表(next数组)
部分匹配表是KMP算法的核心数据结构,它记录了模式串中每个位置的前缀和后缀的最长公共长度(即最长相等前后缀)。这个信息帮助我们在匹配失败时快速决定模式串的指针跳转位置。
二、KMP算法的详细步骤
KMP算法分为两个主要部分:
- 构建部分匹配表(next数组):对模式串进行预处理,计算每个位置的跳转信息。
- 字符串匹配:利用next数组在主字符串中查找模式串。
2.1 构建部分匹配表
部分匹配表的构建是基于模式串自身的模式匹配,目的是找到模式串中每个前缀的最长相等前后缀。
定义:
- 对于模式串 PPP 的位置 iii,next[i]next[i]next[i] 表示以 P[0..i]P[0..i]P[0..i] 为子串时,其最长相等前后缀的长度。
- 最长相等前后缀是指:子串 P[0..i]P[0..i]P[0..i] 的一个前缀 P[0..k−1]P[0..k-1]P[0..k−1] 和后缀 P[i−k+1..i]P[i-k+1..i]P[i−k+1..i] 完全相同,且 kkk 尽可能大(但 k≤ik \leq ik≤i)。
举例:
模式串 P="ABAB"P = "ABAB"P="ABAB":
- i=0,P[0]=′A′i = 0, P[0] = 'A'i=0,P[0]=′A′:
- 前缀和后缀为空,next[0]=0next[0] = 0next[0]=0。
- i=1,P[0..1]="AB"i = 1, P[0..1] = "AB"i=1,P[0..1]="AB":
- 可能的公共前后缀长度为1(“A” 和 “B”),但 P[0]=′A′≠P[1]=′B′P[0] = 'A' \neq P[1] = 'B'P[0]=′A′=P[1]=′B′,无公共前后缀,next[1]=0next[1] = 0next[1]=0。