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

经典算法KMP讲解,包含C++解法ACM模式

写在前面:一个人能走的多远不在于他在顺境时能走的多快,而在于他在逆境时多久能找到曾经的自己。——KMP

  • 讲解
    • 前置知识
    • 模拟next的构建
    • 匹配思路
      • 匹配字符串
      • 构建next数组
    • 模板代码
  • 题目一:KMP字符串
  • 题目二:找出字符串中第一个匹配项的下标

讲解

前置知识

首先,什么是KMP算法呢,就是字符串匹配算法,比如字符串a=abcd,字符串b=eeeeabcdeee,问a在b中出现的第一个下标,这就是KMP啦,字符串是顺序必须完全一样。如果只要求组合或者排列,比如ab和ba一样,这种情况一般用滑动窗口,俺们也写了滑动窗口讲解,保证学完嘎嘎乱杀滑动窗口->传送门

有些概念提前说一下:

  1. s[ ]是模式串,即比较长的字符串。
  2. p[ ]是匹配串,即比较短的字符串。(名字不重要,只要知道是找s里面的p就行
  3. 前缀:指除了最后一个字符以外,一个字符串的全部头部组合。
  4. 后缀:指除了第一个字符以外,一个字符串的全部尾部组合。
  5. “部分匹配值”:前缀和后缀的最长共有元素的长度。
  6. next数组,它存储的是每一个下标对应的“部分匹配值”,是KMP算法的核心

核心:
在每次失配时,不是把p串往后移一位,而是把p串往后移动至下一次可以和前面部分匹配的位置,这样就可以跳过大多数的失配步骤。而每次p串移动的步数就是通过查找next[ ]数组确定的。

首先看看暴力模式下的KMP
核心思路是不匹配时,同时回退s和p的指针,嵌套for,时间复杂度是O(MN)

// 暴力匹配(伪码)
// s是较长的串,p是较短的子串
int search(String s, String p) {int N = s.length;int M = p.length;for (int i = 0; i <= N - M; i++) {// i 是 s 串匹配的起点int j;for (j = 0; j < M; j++) {// j 是 p 子串匹配的起点if (pat[j] != txt[i+j])break;}// p 全都匹配了if (j == M) return i;}// s 中不存在 p 子串return -1;
}

假设:
s:a a a c a a a b
p:a a a b
s用指针 i 遍历,p用指针 j 遍历
当匹配到s[3]和p[3]的时候,两者不相等,而且p串中根本没有c,所以实际上根本没有必要回退 i 指针到 s[1] 的位置。

模拟next的构建

KMP的核心在于构建next数字,next数组记录了,当s和p不匹配时,p应该如何移动。
从头到尾s是不动的,且next数组的构造只和p有关
先说一下next数组的含义:next[j],是p[1, j ]串中前缀和后缀相同的最大长度,即 p[1, next[ j ] ] = p[ j - next[ j ] + 1, j ]。
在这里插入图片描述
解释:next[5]表示p[1,5]也就是abaab的最大前缀abaa和最大后缀也就是baab相同的最大长度,这里长度是ab,所以next[5] = 2

手动模拟求next数组:
p = abcab

p的字符abcab
下标12345
next[]00001

对next[ 4 ] :abca
前缀 = { a , ab , abc }
后缀 = { a . ca , bca }
next[ 4 ] = 1;
————————————————
对next[ 5 ] :abcab
前缀 = { a , ab , abc , abca }
后缀 = { b , ab , cab , bcab}
next[ 5 ] = 2;

匹配思路

匹配字符串

KMP主要分2步,求next数组和匹配字符串

先将匹配字符串:
s串和p串都是从1开始的,i从1开始,j从0开始
每次s[i]和p[j + 1]比较

如图:
当s到i,p到j+1的时候,发现不匹配了,就去next数组找应该退的位置,字符串中 ① == ② == ③,其中②和③相等是匹配的时候知道的,①和③是在求next数组知道的,所以直接将①移动到③的位置,这个操作由j = next[j]完成
在这里插入图片描述
代码:

for(int i = 1, j = 0; i <= n; i++)
{while(j && s[i] != p[j+1]) j = ne[j];//如果j有对应p串的元素, 且s[i] != p[j+1], 则失配, 移动p串//用while是由于移动后可能仍然失配,所以要继续移动直到匹配或整个p串移到后面(j = 0)if(s[i] == p[j+1]) j++;//当前元素匹配,j移向p串下一位if(j == m){//匹配成功,进行相关操作j = next[j];  //继续匹配下一个子串,后面可能还有p子串}
}

构建next数组

这个地方真的很难理解,真的是抓耳饶腮

next数组是由p数组自己跟自己匹配完成的
代码和匹配的几乎一模一样
因为一个是s和p匹配,一个是p和p匹配
关键在于每次移动 i 前,将 i 前面已经匹配的长度记录到next数组中
在这里插入图片描述
代码:

for(int i = 2, j = 0; i <= m; i++)
{while(j && p[i] != p[j+1]) j = next[j];if(p[i] == p[j+1]) j++;next[i] = j;
}

模板代码

不是谁的AC代码

#include <iostream>
using namespace std;
const int N = 100010, M = 10010; //N为模式串长度,M匹配串长度int n, m;
int ne[M]; //next[]数组,避免和头文件next冲突
char s[N], p[M];  //s为模式串, p为匹配串int main()
{cin >> n >> s+1 >> m >> p+1;  //s+1和p+1会使下标从1开始//求next[]数组for(int i = 2, j = 0; i <= m; i++){while(j && p[i] != p[j+1]) j = ne[j];if(p[i] == p[j+1]) j++;ne[i] = j;}//匹配操作for(int i = 1, j = 0; i <= n; i++){while(j && s[i] != p[j+1]) j = ne[j];if(s[i] == p[j+1]) j++;if(j == m)  //满足匹配条件,打印开头下标, 从0开始{//匹配完成后的具体操作//如:输出以0开始的匹配子串的首字母下标//printf("%d ", i - m); (若从下标从0开始,加1)j = ne[j];            //再次继续匹配}}return 0;
}

题目一:KMP字符串

给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。模式串 P 在字符串 S 中多次作为子串出现。求出模式串 P 在字符串 S 中所有出现的位置的起始下标。
第一行输入n,表示p的长度
第二行输入p
第三行输入m,表示s的长度
第四行输入s
输出所有出现位置的起始下标

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,M = 1e6 + 10;
int n, m;
int ne[N];
char s[M], p[N];
int main(){//p+1和s+1使数组起始下标为1,只有数组可以这样用cin >> n >> p + 1 >> m >> s + 1;//建ne数组for(int i = 2, j = 0; i <= n; i ++){while(j && p[i] != p[j + 1]) j = ne[j];if(p[i] == p[j + 1]) j ++;ne[i] = j;//将当前匹配的前缀后缀长度记录到next数组中}//匹配for(int i = 1, j = 0; i <= m; i ++){while(j && s[i] != p[j + 1]) j = ne[j];if(s[i] == p[j + 1]) j ++;if(j == n){cout << i - n << " ";j = ne[j];}}return 0;
}

题目二:找出字符串中第一个匹配项的下标

在这里插入图片描述
注意,ne数组应该初始化为-1
另外,由于这里字符串都是下标0开始的,所以i,j起始相比较模板代码-1

class Solution {
public:int strStr(string haystack, string needle) {int n = haystack.size();int m = needle.size();vector<int> ne(m, -1);// 建next数组for(int i = 1, j = -1; i < m; i ++){while(j != -1 && needle[i] != needle[j + 1]) j = ne[j];if(needle[i] == needle[j + 1]) j ++;ne[i] = j;}// 匹配for(int i = 0, j = -1; i < n; i ++){while(j != -1 && haystack[i] != needle[j + 1]) j = ne[j];if(haystack[i] == needle[j + 1]) j ++;if(j == m - 1){return i - m + 1;}}return -1;}
};

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Python脚本实现USB自动复制文件
  • ADC模数转换在stm32上的应用
  • C语言基础题:硬币问题(C语言版)
  • 蚂蚁0511笔试-选择题
  • 9-springCloud集成nacos config
  • btslab靶场-通过xss获取他人cookie并利用
  • 【vue2+elementui】记录el-upload导入文件:只上传一个文件,且再次上传会覆盖上一个文件
  • 机械学习—零基础学习日志(高数18——无穷小与无穷大)
  • C++笔记---类和对象(中)
  • 【Matlab】快速傅里叶变换fft代码(单边谱)
  • 猫头虎分享疑难杂Bug:error: subprocess-exited-with-error 解决方案
  • docker 建木 发版 (详细教程)
  • Open Interpreter - 开放解释器
  • 无人机工程师技术高级证书详解
  • Python爬虫基础:爬取网页内容解析标题
  • 9月CHINA-PUB-OPENDAY技术沙龙——IPHONE
  • 《剑指offer》分解让复杂问题更简单
  • 【159天】尚学堂高琪Java300集视频精华笔记(128)
  • AzureCon上微软宣布了哪些容器相关的重磅消息
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • go append函数以及写入
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • Java超时控制的实现
  • python 学习笔记 - Queue Pipes,进程间通讯
  • ReactNative开发常用的三方模块
  • React-生命周期杂记
  • windows下mongoDB的环境配置
  • 测试如何在敏捷团队中工作?
  • 从零开始的无人驾驶 1
  • 计算机在识别图像时“看到”了什么?
  • 前端临床手札——文件上传
  • 驱动程序原理
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • HanLP分词命名实体提取详解
  • 正则表达式-基础知识Review
  • #define、const、typedef的差别
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (javascript)再说document.body.scrollTop的使用问题
  • (k8s)Kubernetes本地存储接入
  • (八)Flask之app.route装饰器函数的参数
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (十八)三元表达式和列表解析
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
  • .FileZilla的使用和主动模式被动模式介绍
  • .net core 使用js,.net core 使用javascript,在.net core项目中怎么使用javascript
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .net core使用EPPlus设置Excel的页眉和页脚
  • .NET MVC第五章、模型绑定获取表单数据
  • .Net Web窗口页属性