当前位置: 首页 > news >正文

【数据结构】串——模式匹配

目录

    • 串的模式匹配算法
      • 模式匹配
      • 朴素匹配 / 暴力匹配
      • KMP 算法

串的模式匹配算法

模式匹配

定义

模式匹配是指在一个“主串”(Text)中查找“模式串”(Pattern)出现的位置,并判断是否匹配。

换句话说:

  • 主串 S:你要搜索的长文本,例如 "abcabcabc"
  • 模式串 T:你要找的子串,例如 "cab"

问题:找出 T 第一次出现在 S 中的位置。

形式化描述

  • 主串:S = s1 s2 ... sn
  • 模式串:T = t1 t2 ... tm
  • 找出最小的 i,使得 S[i..i+m-1] = T[1..m]
  • 若找不到,返回 0

例子

主串:S = "abcabcabc"
模式串:T = "cab"

  • 从 S 的第 1 个字符开始比:"abc" vs "cab" → 不匹配
  • 从 S 的第 2 个字符开始比:"bca" vs "cab" → 不匹配
  • 从 S 的第 3 个字符开始比:"cab" vs "cab" → 匹配 ✅

返回位置:3(位序从1开始)

模式匹配算法分类

  1. 朴素匹配 / 暴力匹配(Naive / Brute Force)
    • 原理:从主串每个位置挨个尝试匹配模式串
    • 时间复杂度最坏 O(n·m)
    • 优点:实现简单,容易理解
    • 缺点:效率低
  2. KMP 算法
    • 利用模式串内部的重复信息,避免无谓回溯
    • 时间复杂度 O(n + m)
  3. 其他高级算法(Boyer-Moore、Sunday 算法等)
    • 进一步优化,适合长文本匹配

朴素匹配 / 暴力匹配

朴素匹配就是在 主串 中查找 模式串 出现的位置的最直接方法。

概念化描述

  • 主串:S = s1 s2 ... sn
  • 模式串:T = t1 t2 ... tm

问题:找出主串中第一个连续子串等于模式串的位置(位序从 1 开始)。

如果找不到,就返回 0。

简单说,就是 从头到尾挨个尝试匹配
所以也叫 暴力匹配(Brute Force),因为它不使用任何优化策略。

算法思路:

假设主串长度 n,模式串长度 m

  1. 从主串第一个字符(位序1)开始,尝试匹配模式串。
  2. 如果模式串全部匹配成功 → 返回当前位置。
  3. 如果匹配失败 → 主串起点右移 1 个字符,模式串重新从头开始。
  4. 重复步骤2,直到主串末尾。

匹配公式:

假设当前主串起点为 i,模式串索引为 j(下标从0开始):

  • 如果 S[i+j] == T[j]j++,继续比较下一个字符
  • 如果 S[i+j] != T[j]i = i + 1j = 0,从主串下一个起点重新尝试

时间复杂度:

  • 最坏情况:模式串每个字符都要和主串多次比较
  • 时间复杂度:O(n * m)
  • 空间复杂度:O(1),只需要几个计数器

所以效率低,但直观易理解。

// 朴素匹配函数
int Index(HString S, HString T, int pos) {int i = pos - 1;  // 主串起点(位序从1开始)int j = 0;        // 模式串起点while(i < S.length && j < T.length) {if(S.ch[i] == T.ch[j]) { // 匹配i++; j++;} else {                 // 不匹配,回溯i = i - j + 1;       // 主串起点右移1j = 0;               // 模式串从头开始}}if(j == T.length)return i - T.length + 1; // 返回匹配位置elsereturn 0;               // 未找到
}

**匹配过程:**示例:S = “abcabcabc”,T = “cab”

初始状态:

主串 S:a b c a b c a b c
模式串 T:c a b
匹配起点 i = 0
匹配指针 j = 0

Step 1:i=0, j=0 比较 S[0] vs T[0] → a vs c,不匹配

主串: [a] b c a b c a b c
模式串: [c] a b
结果: × 不匹配,回溯 → i = i - j + 1 = 1, j = 0

Step 2:i=1, j=0 比较 S[1] vs T[0] → b vs c,不匹配

主串: a [b] c a b c a b c
模式串: [c] a b
结果: × 不匹配,回溯 → i = 2, j = 0

Step 3:i=2, j=0 比较 S[2] vs T[0] → c vs c,匹配

主串: a b [c] a b c a b c
模式串: [c] a b
结果: ✓ 匹配 → i=3, j=1

Step 4:i=3, j=1 比较 S[3] vs T[1] → a vs a,匹配

主串: a b c [a] b c a b c
模式串: c [a] b
结果: ✓ 匹配 → i=4, j=2

Step 5:i=4, j=2 比较 S[4] vs T[2] → b vs b,匹配

主串: a b c a [b] c a b c
模式串: c a [b]
结果: ✓ 匹配成功
匹配位置 = i - j + 1 = 3

匹配完成,模式串 "cab" 在主串 "abcabcabc" 的第 3 个字符开始出现。

  1. 模式匹配就是在主串里找子串出现的位置。

  2. 朴素匹配是最基础的方法,通过挨个尝试和回溯实现。

  3. 理解匹配过程是学习 KMP 等高效算法的前提。

KMP 算法

KMP算法是解决字符串模式匹配的高效算法。它的核心思想:

当匹配失败时,不需要把主串的起点回溯到下一个字符,而是利用 模式串自身的重复信息,直接跳过已经匹配过的字符,从而避免重复比较。

  • 主串:S = s1 s2 ... sn
  • 模式串:T = t1 t2 ... tm
  • KMP 能在 O(n + m) 时间复杂度内完成匹配,而朴素匹配最坏是 O(n·m)。

KMP的核心:Next数组(部分匹配表)

  • Next数组记录模式串内部的 前缀和后缀的最大相等长度

    • 定义:next[j] 表示模式串 T[0…j] 的最大相等真前缀和真后缀长度(不包括整个串本身)。

    • 用途:匹配失败时,模式串向右滑动多少位,避免重复比较。

Next数组计算方法:

i 为当前考察位置,j 为前缀长度:

  1. 初始化:next[0] = 0i = 1j = 0
  2. 如果 T[i] == T[j]j++next[i] = ji++
  3. 如果 T[i] != T[j]
    • 如果 j != 0j = next[j-1]
    • 如果 j == 0next[i] = 0i++

Next数组本质上告诉我们:匹配失败时,模式串可以跳到哪儿继续匹配。

void ComputeNext(char T[], int m, int next[]) {int i = 1, j = 0;next[0] = 0; // 第一个位置没有真前后缀while(i < m) {if(T[i] == T[j]) {j++;next[i] = j;i++;} else {if(j != 0)j = next[j-1];else {next[i] = 0;i++;}}}
}

KMP 算法步骤:

  1. 计算模式串的 Next 数组
  2. 主串 S 指针 i = 0,模式串 T 指针 j = 0
  3. 循环比较 S[i] 和 T[j]:
    • 相等 → i++, j++
    • 不等 → 如果 j != 0j = next[j-1];否则 i++
  4. 如果 j == m → 匹配成功,返回 i - m + 1
int KMP(char S[], int n, char T[], int m) {int next[100];ComputeNext(T, m, next);int i=0, j=0;while(i<n) {if(S[i] == T[j]) {i++; j++;} else {if(j != 0)j = next[j-1];elsei++;}if(j == m) // 匹配成功return i - m;}return -1; // 未找到
}

示例:S = “abcabcabc”,T = “abcab”

  • 先计算 Next 数组,模式串 T = "a b c a b"

    索引: 0 1 2 3 4
    T:    a b c a b
    Next: 0 0 0 1 2
    

    Next 数组告诉我们匹配失败时,模式串跳到哪里继续比较。

  • 匹配过程

    1. 初始状态:

      S: a b c a b c a b c
      T: a b c a b
      i = 0 (主串指针)
      j = 0 (模式串指针)
      
    2. Step 1:i=0, j=0

      S[i]=a, T[j]=a → ✓ 匹配
      i=1, j=1
      主串: [a] b c a b c a b c
      模式串: [a] b c a b
      
    3. Step 2:i=1, j=1

      S[i]=b, T[j]=b → ✓ 匹配
      i=2, j=2
      主串: a [b] c a b c a b c
      模式串: a [b] c a b
      
    4. Step 3:i=2, j=2

      S[i]=c, T[j]=c → ✓ 匹配
      i=3, j=3
      主串: a b [c] a b c a b c
      模式串: a b [c] a b
      
    5. Step 4:i=3, j=3

      S[i]=a, T[j]=a → ✓ 匹配
      i=4, j=4
      主串: a b c [a] b c a b c
      模式串: a b c [a] b
      
    6. Step 5:i=4, j=4

      S[i]=b, T[j]=b → ✓ 匹配
      i=5, j=5 → j == T.length → 匹配成功
      匹配位置 = i - j = 0
      

    如果匹配失败的情况:

    假设模式串 T = "abca",S = "abcabcabc",演示失败后的跳转:

    1. 当 i=3, j=3 比较 S[3]=a, T[3]=a → 匹配 ✓
    2. i=4, j=4 比较 S[4]=b, T[4]=? → T[4]不存在 → 匹配失败
    3. 根据 Next 数组 next[3]=1 → j = 1,模式串跳到 T[1] 继续匹配
    4. i 不回退 → 保持 i=4,继续比较 S[4] 与 T[1]

    这就是 KMP 的核心:主串指针不回退,模式串指针根据 Next 数组跳跃

http://www.dtcms.com/a/351556.html

相关文章:

  • 微服务-23.网关登录校验-自定义GlobalFilter
  • yggjs_rbutton React按钮组件v1.0.0 示例和教程
  • Java全栈开发面试实录:从基础到实战的深度探索
  • JVM 学习与提升路线总结:从入门到精通的系统化指南
  • 前端-如何将前端页面输出为PDF并打包的压缩包中
  • PDF转图片、图片转PDF(免费)
  • AI+drawio生成流程图探索
  • Python 操作 PPT 文件:从新手到高手的实战指南
  • 重构审计体验!批量生成报表项目底稿的凭证检查表
  • 计算机术语 / 数学术语中的 trivial 与 non-trivial
  • MD5校验算法
  • Node.js(3)—— fs模块
  • Docker:部署Java后端
  • 关于电脑连接手机热点没有网络的问题
  • 每日一题DEEP_ML-卡方分布(Chi-Squared Distribution)
  • 数据结构初阶:详解单链表(一)
  • Linux->多线程3
  • Ubuntu操作系统下MySQL、MongoDB、Redis
  • 如何拯救一家濒临破产的科技公司?
  • C++工程实战入门笔记5-函数(二)
  • React前端开发_Day1
  • 迅为R3588S开发板ADB使用手册-运行shell
  • ctfshow_萌新web16-web20-----文件包含日志注入
  • 运算符(1)
  • 函数默认值的陷阱:用None和Docstring来描述可变默认值的参数 (Effective Python 第24条)
  • Qt 窗口 - 1
  • [Maven 基础课程]IDEA 配置 Maven
  • 基于 W55MH32Q-EVB 实现 FatFs 文件系统+FTP 服务器
  • C++ SNIFE
  • 数据结构——链表(2)——双向链表