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

【考研408数据结构-05】 串与KMP算法:模式匹配的艺术

📚 【考研408数据结构-05】 串与KMP算法:模式匹配的艺术

🎯 考频:⭐⭐⭐⭐⭐ | 题型:选择题、算法设计题 | 分值:约6-13分

引言

想象你在Word文档中使用"查找"功能搜索某个关键词,瞬间就能定位到所有匹配位置。这背后的核心技术就是字符串模式匹配算法。在408考试中,串的模式匹配特别是KMP算法是几乎每年必考的重点,不仅在选择题中频繁出现,更是算法设计题的高频考点。据统计,近5年的408真题中,有4次直接考察了KMP算法的next数组求解和算法应用。

本文将帮你彻底掌握串的存储结构、BF算法和KMP算法,重点攻克next数组这个难点,让你在考场上游刃有余。

学完本文,你将能够:

  1. ✅ 准确理解串的存储结构和基本操作
  2. ✅ 熟练手工求解next和nextval数组
  3. ✅ 编写完整的KMP算法代码
  4. ✅ 快速解答相关真题

一、知识精讲

1.1 概念定义

串(String) 是由零个或多个字符组成的有限序列,是一种特殊的线性表。

  • 主串(Text):被搜索的字符串,通常用T表示
  • 模式串(Pattern):要查找的字符串,通常用P表示
  • 模式匹配:在主串中查找与模式串相同的子串的过程

💡 408考纲要求

  • 了解:串的基本概念
  • 理解:串的存储结构
  • 掌握:BF算法原理
  • 应用:KMP算法及next数组求解

⚠️ 易混淆点

  • 串的长度:字符的个数(不包括结束符’\0’)
  • 串的存储:顺序存储时下标从0还是从1开始(408常用从1开始)

1.2 串的存储结构

串主要有三种存储方式:

存储方式特点适用场景
定长顺序存储用固定长度数组存储串长度变化不大
堆分配存储动态分配存储空间串长度变化较大
块链存储链表形式,每个节点存多个字符频繁插入删除
// 408考试常用:定长顺序存储(下标从1开始)
#define MAXLEN 255
typedef struct {char ch[MAXLEN + 1];  // ch[0]不用,从ch[1]开始存储int length;            // 串的实际长度
} SString;

1.3 BF算法(Brute Force)

暴力匹配算法是最直观的模式匹配方法:

核心思想:从主串的第一个字符开始,依次与模式串比较。若匹配失败,主串指针回溯,从下一个位置重新开始匹配。

时间复杂度:O(n×m),其中n是主串长度,m是模式串长度

缺点:存在大量重复比较,效率低下

1.4 KMP算法原理 🎯

KMP算法(Knuth-Morris-Pratt)是一种高效的模式匹配算法,由D.E.Knuth、J.H.Morris和V.R.Pratt同时发现。

核心创新

  1. 主串指针永不回溯,始终向前移动
  2. 利用已匹配的信息,通过next数组实现模式串的智能滑动
  3. 时间复杂度降至O(n+m)

关键概念 - 前缀与后缀

  • 前缀:串的前k个字符组成的子串
  • 后缀:串的后k个字符组成的子串
  • 最长公共前后缀:一个串的前缀和后缀的最长相同部分

1.5 next数组详解 ⭐⭐⭐⭐⭐

next数组是KMP算法的灵魂,它记录了模式串中每个位置匹配失败后应该跳转到的位置。

next[j]的含义
当模式串的第j个字符匹配失败时,应该用模式串的第next[j]个字符继续与主串比较。

手工求解next数组的方法

  1. next[1] = 0(固定值)
  2. next[2] = 1(固定值)
  3. 对于j>2:找出P[1…j-1]的最长公共前后缀长度k,则next[j] = k + 1

示例:求模式串"ababcab"的next数组

j1234567
模式串ababcab
next[j]0112312

1.6 nextval数组优化

nextval数组是对next数组的优化,避免了无效的比较。

优化原则
如果P[j] = P[next[j]],则nextval[j] = nextval[next[j]];
否则,nextval[j] = next[j]

二、代码实现

#include <stdio.h>
#include <string.h>#define MAXLEN 255// 串的定义(下标从1开始)
typedef struct {char ch[MAXLEN + 1];  // ch[0]不用int length;
} SString;// BF算法实现
int BF(SString T, SString P) {int i = 1, j = 1;  // i指向主串,j指向模式串while (i <= T.length && j <= P.length) {if (T.ch[i] == P.ch[j]) {i++;j++;} else {i = i - j + 2;  // 主串指针回溯j = 1;          // 模式串从头开始}}if (j > P.length) {return i - P.length;  // 匹配成功,返回起始位置}return 0;  // 匹配失败
}// 求next数组
void getNext(SString P, int next[]) {int j = 1, k = 0;next[1] = 0;while (j < P.length) {if (k == 0 || P.ch[j] == P.ch[k]) {j++;k++;next[j] = k;} else {k = next[k];  // k回溯}}
}// 求nextval数组(优化版本)
void getNextval(SString P, int nextval[]) {int j = 1, k = 0;nextval[1] = 0;while (j < P.length) {if (k == 0 || P.ch[j] == P.ch[k]) {j++;k++;if (P.ch[j] != P.ch[k]) {nextval[j] = k;} else {nextval[j] = nextval[k];  // 优化}} else {k = nextval[k];}}
}// KMP算法实现
int KMP(SString T, SString P, int next[]) {int i = 1, j = 1;  // i指向主串,j指向模式串while (i <= T.length && j <= P.length) {if (j == 0 || T.ch[i] == P.ch[j]) {i++;j++;} else {j = next[j];  // j回溯到next[j]位置}}if (j > P.length) {return i - P.length;  // 匹配成功}return 0;  // 匹配失败
}// 测试函数
int main() {SString T, P;strcpy(T.ch + 1, "ababcababa");T.length = 10;strcpy(P.ch + 1, "ababa");P.length = 5;int next[MAXLEN];getNext(P, next);int pos = KMP(T, P, next);if (pos) {printf("Pattern found at position: %d\n", pos);} else {printf("Pattern not found\n");}return 0;
}

复杂度分析

  • BF算法:时间O(n×m),空间O(1)
  • KMP算法:时间O(n+m),空间O(m)
  • 求next数组:时间O(m),空间O(m)

三、图解说明

【图1】BF算法执行流程

主串T:    a b a b c a b a b a
模式串P:  a b a b aStep 1: i=1, j=1
T: [a] b a b c a b a b a
P: [a] b a b a✓ 匹配Step 2: i=5, j=5  
T: a b a b [c] a b a b a
P: a b a b [a]✗ 失配,i回溯到2,j=1Step 3: i=2, j=1
T: a [b] a b c a b a b a
P:   [a] b a b a✗ 失配,i=3,j=1

【图2】KMP算法next数组作用

当P[5]='a'与T[5]='c'失配时:
不需要从头开始,而是让j=next[5]=3继续比较T: a b a b c a b a b a
P: a b a b a (失配前)↓
P:     a b a b a (使用next[5]=3后)

【图3】前缀后缀示意图

串"ababa"的前缀后缀分析:
位置4: "abab"
前缀:a, ab, aba
后缀:b, ab, bab
最长公共前后缀:"ab",长度=2
所以next[5]=2+1=3

四、真题演练

【2023年408真题】

题目:设主串T=“abaabaabcab”,模式串P=“abaabcab”,采用KMP算法进行匹配,求模式串的next数组。

解题思路

  1. 按照next数组定义,逐位计算
  2. 注意下标从1开始

标准答案

j12345678
P[j]abaabcab
next[j]01122312

评分要点

  • next[1]=0, next[2]=1(2分)
  • 正确计算其他位置(每个1分)

【2021年408真题变式】

题目:已知模式串的next数组为{0,1,1,2,3,1},主串匹配到第10个字符时失配,此时模式串应从第几个字符开始继续匹配?

解答:根据KMP算法,当P[6]失配时,应从P[next[6]]=P[1]开始继续匹配。

⚠️ 易错点

  1. 注意下标是从0还是从1开始
  2. next数组和nextval数组的区别
  3. 手工计算时容易遗漏某些前后缀

五、在线练习推荐

LeetCode相关题目

  • 28. 实现strStr() - 简单
  • 459. 重复的子字符串 - 简单
  • 214. 最短回文串 - 困难

练习顺序建议

  1. 先练习手工求解next数组(大量练习)
  2. 实现基础BF算法
  3. 实现KMP算法
  4. 尝试nextval数组优化
  5. 解决实际应用题

六、思维导图

中心主题:串与KMP算法
├── 一级分支1:基础概念
│   ├── 二级分支1.1:串的定义
│   ├── 二级分支1.2:主串与模式串
│   └── 二级分支1.3:存储结构
├── 一级分支2:BF算法
│   ├── 二级分支2.1:算法思想
│   ├── 二级分支2.2:时间复杂度O(nm)
│   └── 二级分支2.3:指针回溯问题
├── 一级分支3:KMP算法
│   ├── 二级分支3.1:核心思想
│   ├── 二级分支3.2:next数组
│   ├── 二级分支3.3:nextval优化
│   └── 二级分支3.4:时间复杂度O(n+m)
└── 一级分支4:应用场景├── 二级分支4.1:文本搜索├── 二级分支4.2:DNA序列匹配└── 二级分支4.3:网络入侵检测

七、复习清单

✅ 本章必背知识点清单

概念理解
  • 能准确说出串的定义和存储方式
  • 理解前缀、后缀、最长公共前后缀的概念
  • 掌握主串指针回溯与不回溯的区别
代码实现
  • 能手写BF算法代码
  • 能手写求next数组的代码
  • 能手写KMP算法主体代码
  • 记住BF时间复杂度为O(n×m)
  • 记住KMP时间复杂度为O(n+m)
应用能力
  • 会手工求解任意模式串的next数组
  • 会手工求解nextval数组
  • 能分析不同场景下算法的选择
  • 掌握next[1]=0, next[2]=1的固定规律
真题要点
  • 理解next数组每个值的具体含义
  • 掌握KMP算法的执行过程模拟
  • 记住常见陷阱:下标从0还是从1开始

八、知识拓展

前沿应用

KMP算法的思想被广泛应用于:

  • 生物信息学:DNA/RNA序列比对
  • 网络安全:入侵检测系统的模式匹配
  • 搜索引擎:关键词快速定位

常见误区

  1. ❌ next数组的值等于最长公共前后缀长度
    ✅ next数组的值等于最长公共前后缀长度+1
  2. ❌ KMP算法在所有情况下都比BF快
    ✅ 对于短串或随机串,BF可能更快(常数小)
  3. ❌ nextval数组总是比next数组好
    ✅ nextval增加了预处理时间,要权衡使用

记忆技巧

“看前缀,找后缀,最长公共加个1” - 记住next数组求解口诀

结语

串的模式匹配是408数据结构中的必考重点,KMP算法更是其中的核心。通过本文的学习,相信你已经掌握了:

  1. 🎯 串的基本概念和存储结构
  2. 🎯 BF算法的原理和局限性
  3. 🎯 KMP算法的核心思想
  4. 🎯 next数组的手工求解方法
  5. 🎯 完整的代码实现

KMP算法的思想——利用已有信息避免重复工作,这不仅是算法设计的精髓,也将贯穿整个数据结构的学习。下一篇文章,我们将进入树的世界,探索《树与二叉树(上):遍历算法全解析》,掌握另一个408必考重点。

💪 学习建议:KMP算法理解需要时间,建议多画图、多手工模拟。记住,考场上手工求next数组的准确率比代码实现更重要!加油,考研人!


备注:本文所有代码均符合C99标准,适用于408统考要求。

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

相关文章:

  • [论文阅读] 人工智能 + 软件工程 | 从用户需求到产品迭代:特征请求研究的全景解析
  • 【软考架构】软件工程:软件项目管理
  • 用倒计时软件为考研备考精准导航 复习 模拟考试 日期倒计时都可以用
  • SBOM风险预警 | NPM前端框架 javaxscript 遭受投毒窃取浏览器cookie
  • vue3 el-select 默认选中第一个
  • 使用Redis 分布式锁防止短信验证码重复下发问题
  • 《防雷电路设计》---TVS介绍
  • Linux系统之部署nullboard任务管理工具
  • C++/Qt开发:TCP通信连接软件测试方法:ECHO指令
  • C++中的原子操作,自旋锁
  • Vibe Coding:轻松的幻觉,沉重的未来
  • HTML <meta name=“color-scheme“>:自动适配系统深色 / 浅色模式
  • AutoGLM2.0背后的云手机和虚拟机分析(非使用案例)
  • Mac 4步 安装 Jenv 管理多版本JDK
  • 基于YOLO11的手机违规使用检测模型训练实战
  • MySQL诊断系列(3/6):索引分析——5个SQL揪出“僵尸索引”
  • Docker Compose命令一览(Docker Compose指令、docker-compose命令)
  • 动态规划----8.乘积最大子数组
  • 遥感机器学习入门实战教程|Sklearn 案例④ :多分类器对比(SVM / RF / kNN / Logistic...)
  • 详解 scikit-learn 数据预处理工具:从理论到实践
  • 5.4 4pnpm 使用介绍
  • 给你的Unity编辑器添加实现类似 Odin 的 条件显示字段 (ShowIf/HideIf) 功能
  • Scikit-learn 预处理函数分类详解
  • pnpm : 无法加载文件 C:\Program Files\nodejs\pnpm.ps1,因为在此系统上禁止运行脚本。
  • 在 React 中,​父子组件之间的通信(传参和传方法)
  • scikit-learn/sklearn学习|变量去中心化和标准化
  • 2.3 Flink的核心概念解析
  • 详解flink java table api基础(三)
  • Flink Stream API - 顶层Operator接口StreamOperator源码超详细讲解
  • OSPF 典型组网