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

NO.5数据结构串和KMP算法|字符串匹配|主串与模式串|KMP|失配分析|next表

![[Pasted image 20250713032527.png]]

字符串匹配与暴力算法
主串与模式串

字符串匹配是计算机科学中最古老、 研究最广泛的问题之一。
一个字符串是一个定义在有限字母表∑上的字符序列。 例如, ATCTAGAGA 是字母表∑ = {A,C,G,T}上的一个字符串。 字符串匹配问题就是在一个大的字符串 S 中搜索某个字符串 P 的所有出现位置。 在这个问题中, S 又被称为主串, P 又被成为模式串。
![[Pasted image 20250713032617.png]]

暴力算法

一种平凡的思路是模式串和主串从主串起始位置开始匹配, 若发生匹配失败, 则模式串向后移动 1 位再进行比较, 直到匹配成功或主串的每个起始位置都已经被尝试过(匹配失败) 。
![[Pasted image 20250713032650.png]]

如上图中, 对于模式 P = aab 和主串 T = acaabc, 将模式 P 沿着 T 从左到右滑动, 逐个比较字符以判断模式 P 在主串 T 中是否存在。
显然, 假设主串的长度为 n, 模式串的长度 m, 那么显然, 这个算法的时间复杂度为O(nm), 当 m 与 n 同阶时, 复杂度可能达到 O(n²)。

Kmp 算法

求解 next 数组的口诀

  1. 求出模式串的部分匹配值;
  2. 将部分匹配值右移一位;
  3. 在首位字符处补-1;
    注: 若是选择题, 需要观察选项的 next 数组是从-1 开始的还是 0 开始的, 若是从 0 开始的需要将刚才求出的 next 数组中所有的值+1 即可得到答案;
    下面着重讲解部分匹配值的求法;
    先给出相关定义:
    字符串前缀: 字符串最后一个字符之外的所有头部子串;
    字符串后缀: 字符串第一个字符之外的所有尾部子串;
    部分匹配值: 字符串的前缀和后缀的最长相等前后缀长度;
    比如字符串”aabaa”, ’a’的前后缀都是空, ’aa’的前后缀分别是’a’和’a’, 那么此处的部分匹配值就是 1, …, 以此类推, 最后整个字符串的前缀有{a,aa,aab,aaba}后缀有{abaa,baa,aa,a},最长的相等前后缀长度就是 2
    ![[Pasted image 20250713033200.png]]

然后将该部分匹配值表的内容统一右移一位(舍去最右边那一位):
![[Pasted image 20250713033211.png]]

在首位补-1, 即可得到 next 数组:
![[Pasted image 20250713033220.png]]

最终就求得了字符串 aabaa 的 next 表-1,0,1,0,1, 若需要从 0 开始就整体+1, 即 0,1,2,1,2

练习题

主串是 ababbababa,模式串‘ababa’ 的 next 数组是? 在使用 kmp 算法匹配时, 一共发生了几次失配?
![[Pasted image 20250713033525.png]]

![[Pasted image 20250713033540.png]]

失配时模式串指针回退到P[next[j]]P[next[4]] = P[2]
![[Pasted image 20250713033603.png]]

P[next[2]] = P[0]
![[Pasted image 20250713033811.png]]

P[next[0]] = P[-1]
主串和模式串指针都右移一位

kmp算法

失配分析

![[Pasted image 20250713180616.png]]

匹配成功必要条件: T.suffix == T.pre
若T.suffix == T.pre : 直接从S[j]P[j]处开始比较即可
j’的含义:
当模式串P在P[j]处与S[i] 发生失配时,将P[j]移动到与s[j]对齐的位置继续匹配,P右移j-j'长度
在kmp算法中,用next[j]来替换这里的j’

next[j]的含义

![[Pasted image 20250713181102.png]]

![[Pasted image 20250713181118.png]]

![[Pasted image 20250713181134.png]]

![[Pasted image 20250713181148.png]]

![[Pasted image 20250713181213.png]]

显然:
在多个可选的T.pre == T.suffix中,只有选择较长的pre和suffix,才能确保滑动距离最短,满足所有情况。但是,pre和suffix不能取T,否则不能移动

构建next表

![[Pasted image 20250713181252.png]]

next[0]=-1:
P[0]处失配时,需要将P移过当前位置,等效为将P[-1]S[j]对齐。
![[Pasted image 20250713181332.png]]

next[2]=1:
P[2]处失配时,匹配成功部分最长,可重叠前后缀长度为1。
![[Pasted image 20250713181409.png]]

next[3]=0:
匹配成功部分最长,当P[3]处失配时,可重叠前后缀长度为0。
![[Pasted image 20250713181443.png]]

next[4]=1:
P[4]处失配时,匹配成功部分最长,可重叠前后缀长度为1。
![[Pasted image 20250713181513.png]]

next[5]=2:
P[5]处失配时,匹配成功部分最长,可重叠前后缀长度为2。
![[Pasted image 20250713181633.png]]

next[6]=2:
P[6]处失配时,匹配成功部分最长,可重叠前后缀长度为2。
![[Pasted image 20250713181729.png]]

next[7]=3:
匹配成功部分最长,当匹配成功时,可重叠前后缀长度为3。

next[] = [-1,0,1,0,1,2,2,3]
注意:上面的next表数组是从0开始计数的,当数组从1开始计数,需要整体加1:
next[] = [0,1,2,1,2,3,3,4]
考试时根据选项next表第一个元素是-1还是0判断

主算法

![[Pasted image 20250713182001.png]]

失配时:
j = 5 , next[5] = 2,因此j = next[5] = 2
![[Pasted image 20250713182038.png]]

失配时:
j = 5 , next[5] = 2, 因此j = next[5] = 2
![[Pasted image 20250713182111.png]]

继续依次比较,可以匹配成功。

int KMP(char * S,char * T){int next[10];Next(T,next);//根据模式串T,初始化next数组 int i = 0;  //i表示主串指针int j = 0;  //j表示模式串指针while(i < strlen(S) && j < strlen(T)){if(j == -1 || s[i] == T[j]){i++; j++;}else{j = next[j];//如果测试的两个字符不相等,i不动,j变为当前模式串的next值}}if(j >= strlen(T)){//如果条件为真,说明匹配成功return i -(int)strlen(T);} return -1;
}
改进版kmp算法

![[Pasted image 20250713182530.png]]

j=6处发生失配,原因:
P[j] = ‘a’ ≠ S[i]
按朴素kmp算法: j = next[j] = 3
![[Pasted image 20250713182557.png]]

此时发现,P[j]依然等于’a’。 此次匹配注定也是失败的。
因此,若P[next[j]] == P[j],这种替换也是无效的,所以在前面构建next[]表的基础上,还必须加入限制条件:
P[next[j]] ≠ P[j]
![[Pasted image 20250713182636.png]]

nextval[0] = -1:这一点和构建next表方式是一样的
![[Pasted image 20250713182651.png]]

nextval[1] = -1next[0] = 0,但是由于P[0] == P[1],所以不能选用0。
![[Pasted image 20250713182713.png]]

nextval[2] = 1:匹配成功部分为”aa”,因此最长可匹配的前后缀为”a”和”a”,长度为1,且P[1]!=P[2].
![[Pasted image 20250713182736.png]]

nextval[3] = -1:匹配成功部分为”aab”,因此最长可匹配的前后缀为长度为0,且P[0] == P[3],所以不能选用该方案,退而选择nextval[3] = -1
![[Pasted image 20250713182809.png]]

nextval[4] = -1:匹配成功部分为”aaba”,因此最长可匹配的前后缀为“a”和”a”,长度为1,且P[1] == P[4],所以不能选用该方案,又因为P[0] == P[4],所以nextval[4]!=0
![[Pasted image 20250713182839.png]]

nextval[5] = 1:匹配成功部分为”aabaa”,因此最长可匹配的前后缀为“aa”和”aa”,长度为2,又因为P[2] == P[5],所以不能选用该方案,继续寻找备选,次长可匹配前后缀为’a’和’a’,长度为1,而且P[1]!=P[5],所以nextval[5] = 1
![[Pasted image 20250713182910.png]]

nextval[6] = -1:匹配成功部分为”aabaab”,因此最长可匹配的前后缀为“aab”和”aab”,长度为3,又因为P[3] == P[6], 所以不能选用该方案,继续寻找备选,次长可匹配前后长度为0,而且P[0] == P[6],所以nextval[5]!=0,因此nextval[6] = -1

显然,nextval[7] = next[7] = 1

快速构建next表

![[Pasted image 20250713183105.png]]

本质就是找到一个合适的移动距离,使得P的前缀和P的后缀可以匹配
![[Pasted image 20250713183050.png]]

P[j-1] == P[next[j-1]]
![[Pasted image 20250713183138.png]]

next[j] = next[j-1] + 1
![[Pasted image 20250713183205.png]]

P[j-1] != P[next[j-1]]
![[Pasted image 20250713183222.png]]

则继续比较P[j-1]P[next[next[j-1]]

void Next(int* next,char P[])
{int i = 0, j = -1; next[0] = -1;while(i < strlen(P)){if(j == -1 || P[i] == P[j]){next[i + 1] = j+1;i++; j++;}else{j = next[j];}}
}
http://www.xdnf.cn/news/15442.html

相关文章:

  • 前端构建工具 Webpack 5 的优化策略与高级配置
  • 代码随想录算法训练营第十八天
  • Appium源码深度解析:从驱动到架构
  • nginx安装
  • [Subtitle Edit] 语言文件管理.xml | 测试框架(VSTest) | 构建流程(MSBuild) | AppVeyor(CI/CD)
  • COZE token刷新
  • 代码随想录|图论|15并查集理论基础
  • ARC 03 从Github Action job 到 runner pod
  • Java4种设计模式详解(单例模式、工厂模式、适配器模式、代理模式)
  • 【DeepSeek实战】29、金融数据抓取全攻略:从AKShare到API实战,Python量化分析必备指南
  • JavaScript 中一些常见算法的实现及详细解析
  • 详解Linux下多进程与多线程通信(二)
  • Web应用性能优化之数据库查询实战指南
  • 时间的弧线,逻辑的航道——标准单元延迟(cell delay)的根与源
  • 单页面和多页面的区别和优缺点
  • 通用定时器GPT
  • 【Linux学习笔记】认识信号和信号的产生
  • 区块链平台之以太坊深入解读:技术、经济与生态的全面解析
  • 剑指offer57_和为S的两个数字
  • 串口连接工控机
  • 【离线数仓项目】——电商域DIM层开发实战
  • 服务端高效处理拖拽排序
  • 锁相环初探
  • 【6.1.2 漫画分布式事务技术选型】
  • BaseDao 通用更新方法设计与实现
  • 【PMP备考】敏捷思维:驾驭不确定性的项目管理之道
  • Java ThreadLocal详解:从原理到实践
  • 快速过一遍Python基础语法
  • 第34次CCF-CSP认证第4题,货物调度
  • 零基础搭建监控系统:Grafana+InfluxDB 保姆级教程,5分钟可视化服务器性能!​