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

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.dtcms.com/a/278057.html

相关文章:

  • pthread_mutex_unlock函数的概念和用法
  • 大规模电商系统分库分表实战经验分享
  • NFSV4锁机制(三)
  • 编程技术杂谈2.0
  • DVWA靶场通关笔记-XSS DOM(High级别)
  • 垃圾收集器-Serial Old
  • CVE-2022-0609
  • vue2入门(1)vue核心语法详解复习笔记
  • 【开源项目】网络诊断告别命令行!NetSonar:开源多协议网络诊断利器
  • 1.1.1+1.1.3 操作系统的概念、功能
  • c++无锁队列moodycamel::ConcurrentQueue测试结果
  • 在高并发场景下,仅依赖数据库机制(如行锁、版本控制)无法完全避免数据异常的问题
  • Sping AI Alibaba
  • 第11章 AB实验评估指标体系
  • Soul方程式:Z世代背景下兴趣社交平台的商业模式解析
  • Java行业前景如何?零基础又该如何去学Java?
  • 深入理解 RocketMQ:生产者详解
  • 并行并发丨C++ 协程、现场池 学习笔记
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十三课——图像浮雕效果的FPGA实现
  • 语言模型常用的激活函数(Sigmoid ,GeLU ,SwiGLU,GLU,SiLU,Swish)
  • 算法-汽水瓶兑换
  • Spring AI 项目实战(十七):Spring Boot + AI + 通义千问星辰航空智能机票预订系统(附完整源码)
  • 【webrtc】gcc当前可用码率3:x264响应码率改变
  • 系规备考论文:论IT服务部署实施方法
  • 西藏氆氇新生:牦牛绒混搭液态金属的先锋尝试
  • 分布式锁踩坑记:当“防重“变成了“重复“
  • JAVA并发——什么是Java的原子性、可见性和有序性
  • Redis缓存设计与性能优化指南
  • 使用Starrocks替换Clickhouse的理由
  • C++封装、多态、继承