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

C++ KMP算法

一个人能走的多远  不在于他在顺境走的多快  而在于 他在逆境中多久能找到曾经的自己

                                                                                                   -----KMP

1.匹配字符串

问题:

 s1="mississippi"(主串)   m个数据

s2="issip"(子串 )              n个数据

我怎么才确认在s1中是否存在s2呢?

当然我们一眼可以看出来  那计算机怎么办?

暴力方法:我们首先在主串中找到 和子串首字符相同的字符

(标红的位置表示指针指向的位置!)

 s1="mississippi"         ( i为指针指向的位置   该指针设为ptr1)

s2="issip""  (i为指针指向的位置   该指针设为ptr2)

然后我们ptr1 ptr2  同时走  会出现三种情况   

(1) 直到子串全部遍历完 ptr1和ptr2指向的都完全相同 说明就找到了

(2)在遍历过程中  ptr1和ptr2指向的内容不同 这说明我们没有找到 要重新再开始找!

(3)ptr1把主串遍历完 子串没遍历完   这也就没找到!

比如上面这个例子  ptr1 和 ptr2同时走 遇到的就是第二种情况!

 s1="mississippi"(主串)

s2="issip"(子串)

ptr1 和  ptr2指向的不同

那遇到第二种情况  指针ptr2指向的位置肯定是要重新回溯到字串的第一个字符的

那么ptr1呢? ptr1可以不回溯吗?

 我假设ptr1不回溯 接着往后面走 ptr2回溯后不变

s1="mississippi"(主串)

s2="issip"(子串)

好 这个时候匹配到了子串的第一个字符  那么ptr1 ptr2同时往后面走

这个时候和上面一样三种情况

最后一直不回溯走下去的结果是主串中不存在子串

但是实际结果是

s1="mississippi"(主串)

s2="issip"(子串)

明显存在

结论:暴力算法ptr1要回溯!

回溯可以保证正确性(这里不详细介绍)

但是又引出了新的问题

如果回溯次数过多  回溯回去的区间长度太大   最坏情况下 时间复杂度会达到O(n*m)

这个时间复杂度就太高了

那么有没有什么方法可以解决ptr1回溯的问题 让时间复杂度降下来呢?

这便是我们本期学习的KMP  算法  时间复杂度是O(m+n)  空间复杂度是O(n)

2.相同前后缀

KMP解决回溯的核心思路是相同前后缀

比如我们来看下面的主串和子串

我们来看子串的前面四个字符的部分

ABAB

紫色的是前缀  黄色的后缀

当我们发现ptr1指向的和ptr2指向的不同的时候 (上图)    子串和前缀对齐     如果是暴力解法 ptr1就要回溯到第三个元素A了

但是我们发现子串的 部分串(设为it,it==ABABC)(标红的部分)  前缀和后缀相同  我原本it的前缀和对应的主串部分匹配  it的后缀和前缀一模一样

是不是也意味着 it的后缀和 原本it的前缀对应的主串部分  匹配  

我们是不是就可以将原先 主串中和it前缀 对齐的  部分   和it的后缀对齐

原理是什么? 原理是子串前缀和后缀相同    子串的前缀如果能和 主串对应的部分匹配   

那么后缀是不是也能和那部分匹配呢?

如果后面我们发现ptr1 和 ptr2指向不同  我们可以直接前缀变后缀

这个时候我们就不用把主串的指针(ptr1)回溯了! 

3.next数组用法

但是我们怎么知道和后缀对齐要怎么移动 移动多少个位置呢?

这个时候就要引入KMP算法中的next数组了(next数组后面详细解释) 我们这里先介绍next数组的用法

 如果ptr1和ptr2指向的不同的时候  我们找到子串指针 指向元素的位置  的前一个位置的next数组里面储存的值(设为value==1)

上图要找的是next数组里面标红的1   

找到1  意味着 

1.ptr1不动   

2.ptr2指向的位置是  从首元素偏移value个元素的位置

3.ptr2指向的位置和ptr1对齐

然后再ptr1和ptr2同时往后走并且比较其指向的元素是否相同!

4.next数组求法

我们为什么可以这样调整  核心原理是前缀和后缀相同

你猜对了 next数组里面存的就是    最长的相同前后缀    的长度

***注意  我们找的最长的前后缀  不能是这个串本身!!!***

因为如果找的是本身你会发现(按照前面next数组使用的步骤)就是死循环了!

我们以子串ABACABA为例来实现一下 next数组

首先是A 一个元素  没有前后缀   next值为0

  AB没有相同前后缀   next值为0

ABA    两个A分别是最大相同前后缀  next值为1

ABAC 没有相同前后缀 next值为0

ABACA     第一个A和最后一个A分别是最大相同前后缀  next值为1

ABACAB    两个AB是最大的前后缀  next值为2

ABACABA     两个ABA是最大的相同前后缀  next值为3

那么怎么通过算法把next数组弄出来呢?

有个很简单的方法 就是递推法  类似数学的数学归纳法

假设我知道当前串(ABACABA)next的值也就是3  

下面有两种情况!

 (1).最大相同的  前缀和后缀  的下一个值相同的时候 

这个时候不就构成了更长的一个相同前后缀吗!其这个时候next的值是原先next的值+1   如下图

  (2).最大相同的  前缀和后缀  的下一个值不同的时候 

这个时候next的值肯定就比相同情况下的小! 但是具体小多少呢?

我们很容易看出来是2  但是怎么转换成算法和代码呢?

首先我们原先的最大相同前缀和后缀是标红的地方  这个时候我们要找的最长相同前后缀肯定不可能比 原先的(ABACABA)大

 也就是(ABACABAB)的最大相同后缀只是     原先(ABACABA)最大相同后缀(ABACABA)(标红位置)  的后半部分(不含全部 )+末尾的B

为什么不能是只能是后半部分 而不能是全部呢?

因为含全部+末尾的B加起来就比原先 ABACABA的最大相同前后缀要大了 这明显不可能

最大的相同前缀就是     原先(ABACABA)最大相同前缀(ABACABAB)(标红的部分)的部分(含全部)

也就是我们这个时候要找的是这两块背景有颜色区域中  满足前缀等于后缀的  最长前缀和后缀

很熟悉吧 最长相同前后缀不就是ABAB的next数组的值嘛  所以这种情况 就是看

这个的最大的前后缀长度是多少

那这个的最大前后缀怎么看? 

我们知道ABA的next的值

从ABA  到ABAB 多出的字符B不就是和    ABA  最大相同前缀(ABA) 的下一个值(ABA)相同嘛

不就意味着ABAB的  最大相同前后缀长度  是ABA的next值+1

也就是2   也就是意味着ABACABAB的next值就是2!!!

总结一下 下一个字符   和   最大相同前缀的     下一个字符   不同 

我们要找到  原先的   最大相同前缀 (ABACABAB)

并找到此时的字符(整个串的最后一个字符)(ABACABAB

判断 此时的字符在  原先的 最大相同前缀  后的情况下     对应的next的值

当然  上面这个步骤我们实现的时候 可不能直接将此时的字符放到    原先的 最大相同前缀的后面去  这样会造成  数据覆盖!

那怎么处理呢  以上图为例子

找到 原先的   最大相同前缀    (ABACABAB)

再判断   原先的   最大相同前缀   的 最大相同前缀 (ABACABAB) 的下一个元素  (ABACABAB)   

是否和 此时的字符(ABACABAB) 相同   

1.如果相同  那么此时字符对应的next的值就是  原先    最大相同前缀末端的next的值+1

 2.如果不同  是不是就要重复上面的步骤(类似递归一样)

next数组实现方法如上

我们再来整理一下总的思路

我们解决暴力算法回溯的方式是通过最大相同前后缀

什么时候会产生回溯

就是ptr1 ptr2指向的元素不同的时候   

也就意味着我们  ptr1和ptr2指向不同的时候

就要使用  子串  指针指向  的前一个  位置的  next的值
(value)了

然后再

1.ptr1不动   

2.ptr2指向的位置是  从首元素偏移value个元素的位置

3.ptr2指向的位置和ptr1对齐

其他思路和暴力算法是一样的

我们通过最长相同前后缀完美  解决了回溯带来的时间复杂度

成功的将时间复杂度降低到O(m+n)  空间复杂度降到了O(n)  (next数组)

KMP的算法的核心是ptr1永远不后退!!!

 曾经的自己==最大相同前后缀

 逆境==ptr1和ptr2指向位置不同时

然后我们可以根据以上原理实代码来!

#include <iostream>
#include <vector>
#include <string>// 计算 next 数组,用于记录模式串中每个位置之前的子串的最长相同前缀和后缀的长度
void computeNext(const std::string& pattern, std::vector<int>& next) {int m = pattern.length();// next 数组的第一个元素始终为 0next[0] = 0;// j 用于记录最长相同前缀的长度int j = 0;// 从模式串的第二个字符开始遍历for (int i = 1; i < m; i++) {// 当当前字符和最长相同前缀的下一个字符不相等时while (j > 0 && pattern[i] != pattern[j]) {// 回溯到 next[j - 1] 的位置j = next[j - 1];}// 如果当前字符和最长相同前缀的下一个字符相等if (pattern[i] == pattern[j]) {// 最长相同前缀的长度加 1j++;}// 更新 next 数组next[i] = j;}
}// KMP 字符串匹配算法
int kmpSearch(const std::string& text, const std::string& pattern) {int n = text.length();int m = pattern.length();// 创建 next 数组std::vector<int> next(m);// 计算 next 数组computeNext(pattern, next);// j 用于记录模式串的当前匹配位置int j = 0;// 遍历文本串for (int i = 0; i < n; i++) {// 当当前字符和模式串的当前字符不相等时while (j > 0 && text[i] != pattern[j]) {// 回溯到 next[j - 1] 的位置j = next[j - 1];}// 如果当前字符和模式串的当前字符相等if (text[i] == pattern[j]) {// 模式串的匹配位置加 1j++;}// 如果模式串已经完全匹配if (j == m) {// 返回匹配的起始位置return i - m + 1;}}// 未找到匹配,返回 -1return -1;
}int main() {std::string text = "ABABDABACDABABCABAB";std::string pattern = "ABABCABAB";// 调用 KMP 搜索函数int index = kmpSearch(text, pattern);if (index != -1) {std::cout << "Pattern found at index: " << index << std::endl;} else {std::cout << "Pattern not found." << std::endl;}return 0;
}    


http://www.mrgr.cn/news/97212.html

相关文章:

  • #SVA语法滴水穿石# (013)关于 disable iff、matched 、expect 的用法
  • 贪心算法之最小生成树问题
  • 分治-归并排序-逆序对问题
  • 【VUE】RuoYi-Vue3项目结构的分析
  • MessageQueue --- RabbitMQ WorkQueue
  • C++ 排序(1)
  • 我的购物车设计思考:从个人项目到生产实战思考的蜕变
  • 【2016】【论文笔记】差频可调谐THz技术——
  • 基于编程的运输设备管理系统设计(vue+springboot+ssm+mysql8.x)
  • 九、重学C++—类和函数
  • Python解决“组成字符串ku的最大次数”问题
  • Three.js 系列专题 1:入门与基础
  • 多周期多场景的供应链优化问题 python 代码
  • Java的Selenium元素定位-xpath
  • 【深度学习】通过colab将本地的数据集上传到drive
  • AI比人脑更强,因为被植入思维模型【44】成长破圈思维
  • 【FPGA开发】利用状态机思想点亮流水灯/初学hdlbitsFPGA教程网站
  • C++学习之LINUX网络编程-套接字通信基础
  • 【51单片机】3-3【定时器/计数器/中断】超声波测距模块测距
  • Spring Cloud 框架为什么能处理高并发