KMP字符串匹配算法原理与实现
字数 2225 2025-11-18 05:04:19
KMP字符串匹配算法原理与实现
KMP算法是一种高效的字符串匹配算法,由Knuth、Morris和Pratt三位科学家共同提出。它的核心思想是通过预处理模式串(Pattern),构建一个部分匹配表(通常称为next数组),利用匹配失败时的已知信息避免不必要的回溯,将时间复杂度从暴力匹配的O(m*n)优化到O(m+n)。
1. 问题背景与暴力匹配的局限性
字符串匹配问题:给定一个主串S(长度为n)和一个模式串P(长度为m),在S中查找P首次出现的位置。
暴力匹配算法:从S的每个字符开始,逐个与P的字符比较。若某次匹配失败,主串指针回退到本次起始位置的下一个字符,模式串指针回退到开头。最坏情况下需比较m*n次,效率低下。
2. KMP算法的核心思想:利用已匹配信息
当某次匹配失败时,暴力匹配会丢弃本次匹配的所有信息,从头开始。KMP算法观察到:匹配失败时,已匹配的子串是已知的,利用这个信息可以跳过一些绝不会成功的匹配尝试。
- 示例:S = "ABABABC", P = "ABABC"
- 第一次匹配:S[0:4] = "ABAB" 与 P[0:4] = "ABAB" 匹配,但S[4] = 'A' 与 P[4] = 'C' 失败。
- 暴力匹配:主串指针回退到S[1],重新从P[0]开始比较。
- KMP策略:已匹配的"ABAB"中,后缀"AB"(P[2:4])与前缀"AB"(P[0:2])相同。直接将P右移2位,使P[0:2]对齐S[2:4],继续从S[4]和P[2]开始比较,避免回溯。
3. 部分匹配表(next数组)的构建
next数组定义:对于模式串P,next[i]表示子串P[0:i](长度为i+1)中,最长的相等真前缀和真后缀的长度(真前缀/后缀指不包含自身)。
- 计算步骤(递推法,i从0到m-1,j表示当前匹配的前缀长度):
- next[0] = -1(初始条件,无真前缀/后缀)
- 设j = -1, i = 0
- 循环 while i < m-1:
- 若 j == -1 或 P[i] == P[j]:
- i++, j++
- next[i] = j(P[i]前的最长相等前后缀长度为j)
- 否则:
- j = next[j](失配时,j回退到前一个匹配位置)
- 若 j == -1 或 P[i] == P[j]:
- 示例:P = "ABABC"
- i=0, j=-1 → j=-1 → i=1, j=0, next[1]=0
- i=1, j=0 → P[1]='B' != P[0]='A' → j=next[0]=-1
- i=1, j=-1 → j=-1 → i=2, j=0, next[2]=0
- i=2, j=0 → P[2]='A' == P[0]='A' → i=3, j=1, next[3]=1
- i=3, j=1 → P[3]='B' == P[1]='B' → i=4, j=2, next[4]=2
- 最终next = [-1, 0, 0, 1, 2]
4. KMP匹配过程
- 初始化:主串指针i=0,模式串指针j=0
- 循环 while i < n 且 j < m:
- 若 j == -1 或 S[i] == P[j]:
- i++, j++(字符匹配,双指针后移)
- 否则:
- j = next[j](失配时,j回退到next[j]位置,i不变)
- 若 j == -1 或 S[i] == P[j]:
- 匹配结果:
- 若 j == m:返回 i - j(匹配成功)
- 否则:返回 -1(匹配失败)
5. 完整示例演示
S = "ABABABC", P = "ABABC", next = [-1,0,0,1,2]
- i=0, j=0: S[0]='A'=P[0] → i=1, j=1
- i=1, j=1: S[1]='B'=P[1] → i=2, j=2
- i=2, j=2: S[2]='A'=P[2] → i=3, j=3
- i=3, j=3: S[3]='B'=P[3] → i=4, j=4
- i=4, j=4: S[4]='A'≠P[4]='C' → j=next[4]=2
- i=4, j=2: S[4]='A'=P[2]='A' → i=5, j=3
- i=5, j=3: S[5]='B'=P[3]='B' → i=6, j=4
- i=6, j=4: S[6]='C'=P[4]='C' → i=7, j=5
- j=5=m → 匹配成功,返回i-j=2
6. 算法优化(nextval数组)
若P[j] == P[next[j]],则下次匹配必然失败。可优化next数组为nextval:
- 计算nextval[i]:
- 若P[i] == P[next[i]],则nextval[i] = nextval[next[i]]
- 否则nextval[i] = next[i]
- 示例:P="ABABC", next=[-1,0,0,1,2] → nextval=[-1,0,0,0,2](避免重复比较)
7. 复杂度分析
- 时间复杂度:O(m+n)(预处理next数组O(m),匹配过程O(n))
- 空间复杂度:O(m)(存储next数组)
通过以上步骤,KMP算法利用部分匹配表避免了主串指针的回溯,显著提升了匹配效率,尤其适用于模式串重复度高或主串庞大的场景。