字符串模式匹配查找

定义与基本思想

字符串模式匹配(String Matching)是指在主串中查找与模式串相同的子串位置。常见算法有朴素算法、KMP 算法等。

字符串模式匹配演示

此处可扩展为KMP等算法的匹配过程动画。

  • 主串与模式串对齐
  • next数组跳转演示
  • 匹配/失配过程

常见算法

朴素算法(Brute Force)

  • 逐一对齐主串和模式串,依次比较字符
  • 时间复杂度 O(mn)O(mn),mm为主串长度,nn为模式串长度

KMP 算法

  • 利用部分匹配表(next 数组)避免重复比较
  • 时间复杂度 O(m+n)O(m+n)

适用场景

  • 文本编辑器查找、替换
  • DNA 序列分析
  • 信息检索、数据挖掘

算法描述

朴素算法伪代码:

for i = 0 to m-n:
    for j = 0 to n-1:
        if S[i+j] != P[j]:
            break
    if j == n:
        return i // 匹配成功
return -1

KMP 算法伪代码(简化):

构建next数组
i = 0, j = 0
while i < m:
    if S[i] == P[j]:
        i++, j++
        if j == n: return i-n // 匹配成功
    else if j > 0:
        j = next[j-1]
    else:
        i++
return -1

KMP 算法的 next 数组推导

next 数组是 KMP 的核心:当模式串 P[0..j]P[0..j] 在 P[j]P[j] 处失配时,jj 应该回退到 next[j] 的位置。next[j] 等于 P[0..j-1] 的最长相等前后缀长度。

求 next 数组的步骤

  1. next[0] = -1(第一个字符失配,主串指针后移)
  2. 递推求 next[j]:令 k = next[j-1],若 P[j-1] == P[k],则 next[j] = k+1;否则 k = next[k] 继续回退

示例:模式串 “abaabc”

j012345
P[j]abaabc
next[j]-100112

推导过程:

  • next[0] = -1
  • j=1:子串 a 无前后缀,next[1] = 0
  • j=2:子串 ab 最长相等前后缀为 0,next[2] = 0
  • j=3:子串 aba 最长相等前后缀为 a,长度 1,next[3] = 1
  • j=4:子串 abaa 最长相等前后缀为 a,长度 1,next[4] = 1
  • j=5:子串 abaab 最长相等前后缀为 ab,长度 2,next[5] = 2

作用:在 P[j] 失配时,模式串指针回退到 next[j] 继续比较,而主串指针 i 不回溯,从而保证整体 O(m+n)O(m+n)。

KMP 算法代码实现

// 求 next 数组
void GetNext(char P[], int next[], int n) {
    next[0] = -1;
    int i = 0, k = -1;
    while (i < n - 1) {
        if (k == -1 || P[i] == P[k]) {
            i++;
            k++;
            next[i] = k;
        } else {
            k = next[k];  // 回退
        }
    }
}

// KMP 匹配,S 为主串(长 m),P 为模式串(长 n)
int KMP(char S[], char P[], int next[]) {
    int i = 0, j = 0;
    while (i < (int)strlen(S) && j < (int)strlen(P)) {
        if (j == -1 || S[i] == P[j]) {
            i++;
            j++;
        } else {
            j = next[j];  // 主串不回溯,模式串回退
        }
    }
    if (j == (int)strlen(P))
        return i - j;   // 匹配成功,返回起始位置
    return -1;          // 匹配失败
}

时间复杂度分析

  • 朴素算法:O(mn)O(mn)
  • KMP 算法:O(m+n)O(m+n)

习题

习题 1

简述 KMP 算法的基本思想及其优点。

字符串匹配前缀函数模式匹配时间复杂度

解题思路:

本题考查 KMP 算法的基本思想与优势。核心思想是预先为模式串构造 next 数组记录其前缀信息,匹配失败时据此直接跳过已经确认匹配过的部分,而不是从头逐个重新比较。

详细步骤:

  1. 利用 next 数组避免重复比较
  2. 提高匹配效率,时间复杂度O(m+n)O(m+n)
  3. 补充:朴素算法在失配后主串指针要回退、模式串从头重比,代价为 O(mn)O(mn);KMP 通过 next 数组把模式串指针跳到已匹配前缀的最长可延伸位置,使已比较过的字符不必重复比较,从而把复杂度降为 O(m+n)O(m+n)。

最终答案:

KMP 用 next 数组跳过已知匹配前缀,效率高。

习题 2

朴素算法和 KMP 算法的时间复杂度分别是多少?

字符串匹配朴素匹配时间复杂度

解题思路:

本题考查两种字符串匹配算法的复杂度对比。关键在于失配之后是否需要把主串指针回退并从头重比,这一步决定了总代价是乘积级还是线性级。

详细步骤:

  1. 朴素算法O(mn)O(mn)
  2. KMP 算法O(m+n)O(m+n)
  3. 补充:设主串长为 nn、模式串长为 mm。朴素算法最坏情况下主串的每个位置都要与模式串逐字符比较,代价为 O(mn)O(mn);KMP 借 next 数组使主串指针只前进不回退,总比较次数为线性量级,故为 O(m+n)O(m+n)。

最终答案:

朴素O(mn)O(mn),KMPO(m+n)O(m+n)。

习题 3

KMP 算法如何避免主串指针回溯?

字符串匹配前缀函数主串回溯模式串跳转

解题思路:

本题要求说明 KMP 避免主串回溯的机制。答案是把“回退”从主串转移到模式串:主串指针始终只向前移动,需要调整的只是模式串指针。

详细步骤:

  1. 匹配失败时,主串指针不回溯
  2. 模式串指针根据 next 数组跳转
  3. 补充:next 数组记录了模式串各位置的最长相等前后缀长度,失配时据此把模式串指针跳到该长度对应的位置,使主串当前位置可以与新的模式串字符直接比较;由于跳过的正是此前已确认匹配的前缀,这些字符无须重新比较。

最终答案:

KMP 用 next 数组避免主串回溯。

发现内容有问题?

告诉我们哪里需要改进。