KMP算法详解

2023-11-11

什么是KMP算法? 

有句话可以这么形容KMP:一个人能走的多远不在于他在顺境时能走的多快,而在于 他在逆境时多久能找到曾经的自己。

KMP算法是一个字符串匹配算法,取得是三个发明人的名字首字母。KMP算法的作用 是在一个已知字符串中查找子串的位置,也叫做串的模式匹配,后文主串和模式串匹配, 子串和模板串匹配。

暴力做法

KMP可以解决串的模式匹配,但是一般我们解决这个问题首先想到的是暴力做,什么 也不管,直接两个for循环。暴力匹配也叫朴素的模式匹配。

从主串和子串的第一个字符开始,将两字符串的字符一一比对,如果出现某个字符 不匹配,主串回溯到第二个字符,子串回溯到第一个字符再进行一一比对。如果出 现某个字符不匹配,主串回溯到第三个字符,子串回溯到第一个字符再进行一一比对… 一直到子串字符全部匹配成功。最坏的情况下时间复杂度是O(m*n),这样不停的回溯速 度会非常慢,当数据非常大工程量也会非常大。

所以这时候就要想想怎么优化了,再想一下暴力做法,一直在回溯,回溯的步骤太多了, 所以能不能找到一种规律减少回溯的次数,这就是KMP算法

KMP算法

KMP算法一种改进的模式匹配算法,是D.E.Knuth、V.R.Pratt、J.H.Morris于1977年联合 发表,KMP算法又称克努特-莫里斯-普拉特操作。

它的改进在于:每当从某个起始位置开始一趟比较后,在匹配过程中出现失配,不 回溯i,而是利用已经得到的部分匹配结果,将一种假想的位置定位“指针”在模式上 向右滑动尽可能远的一段距离到某个位置后,继续按规则进行下一次的比较

比如当我们在主串下标为3匹配子串,往后继续匹配主串,在下标是10的位置主串和 子串不匹配,这个时候就要把子串往后移动到首字母相同的位置继续匹配,但其实我 们中间已经匹配了很多字符了,里面是有一些额外信息在里面的。我们利用这些额外 信息就可以帮我们少枚举一些东西。

还是以上面为例,当我们在下标10主串和子串不匹配,我们要从3往后面找和子串首 字母相同的位置然后继续匹配,如果这个位置在10前面的话,我们可以发现这个位置 到10的距离和第一次匹配的子串有重合的部分,我们假设这个位置是6

我们可以很明显的发现6-10的数组在第一次匹配的子串和第二次匹配的子串出现重合 的情况,也就是在子串中间和前面是重合的,所以我们只要将子串直接移动到下标为6 的位置就行,这样我们就减少了枚举的次数。所以我们只要求出子串往后移动多少位 可以出现重合即可

这个问题只和我们的子串也就是模板串有关系,如果我们可以预处理出来这种子串的中 间和开头有一段距离相同,我们求出这一段的最大值就行,这一段的距离越大的话说明 子串往后移动的越少

所以要给我们的子串,也就是模板串的每个点进行预处理,以某个点为终点的后缀和前 缀相等,相同的最大长度是多少,这个就是KMP算法中最难的next数组的含义。

next[i]表示的就是以i为终点的后缀和从1开始的前缀相等,且相同的部分最长,这里 我们默认子串下标从1开始。比如next[i]=j就表示在子串中p[1,j]=p[i-j+1,i],这里我们 用p数组暂时表示子串,这个就表示子串中下标从1到j这一段和i-j+1到i是相等的, 而且长度最长。所以下次从j+1再开始继续匹配。

next数组

next 数组的值是除当前字符外(注意不包括当前字符)的公共前后缀最长长度

求next数组最重要的一点是找最长公共前后缀,什么是前后缀呢

前缀是除了最后一个字符的所有子串。

后缀是除了第一个字符的所有子串。

如图

举个栗子

比如子串是ababababab,我们求他的next数组,子串下标从1开始

很明显next[1]=0,因为第一个默认是0

next[2]=0,因为没有公共前后缀

next[3]=1,最长公共前后缀是a

next[4]=2,最长公共前后缀是ab

Next[5]=3,最长公共前后缀是aba,依次类推next[6]=4.....

我们可以发现next数组的值就是子串退回时的下标

动画演示

我们可以看一下暴力做法和KMP做法的一个区别

例题

给定一个模式串 SS,以及一个模板串 PP,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模板串 PP 在模式串 SS 中多次作为子串出现。

求出模板串 PP 在模式串 SS 中所有出现的位置的起始下标。

输入格式

第一行输入整数 NN,表示字符串 PP 的长度。

第二行输入字符串 PP。

第三行输入整数 MM,表示字符串 SS 的长度。

第四行输入字符串 SS。

输出格式

共一行,输出所有出现位置的起始下标(下标从 00 开始计数),整数之间用空格隔开。

数据范围

1≤N≤1051≤N≤105
1≤M≤1061≤M≤106

输入样例:

3
aba
5
ababa

输出样例:

0 2

代码实现

#include <iostream>
using namespace std;
const int N=1e7+10;
char p[N],s[N];
//next数组表示的是不匹配时子串下标退回的位置
//比如next[i]=j就表示在子串中p[1~j]=p[i-j+1~i]
//所以子串直接退回到j下标继续和主串进行模式匹配直到匹配成功为止
int ne[N];
int n,m;
int main()
{
    cin>>n>>p+1>>m>>s+1;
    //求next数组的过程
    //类似于KMP匹配过程
    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;
    }
    //KMP匹配过程
    for(int i=1,j=0;i<=m;i++)
    {
        //如果子串没有退回到起点,并且主串和子串不匹配
        while(j&&s[i]!=p[j+1]) j=ne[j];//j退回到重合部分的终点位置
        if(s[i]==p[j+1]) j++;
        //匹配成功
        if(j==n)
        {
            cout<<i-n<<" ";
            j=ne[j];
        }
    }
    return 0;
}

 思路

匹配操作就不用说了,next数组的求法和匹配的操作几乎一模一样,通过子串自己和自己进行匹配操作出来的

假设[m,n]是相同的,即next[i-1]=j,如果此时p[i]!=p[j+1],在进行到这步操作时前面的next 的值已经求好了,所以我们要再ne一下,把j退到next[j]的位置,即next[j],退完之后 我们再看p[ne[j]+1]和p[j+1]是否匹配,不匹配的话在ne一下,把j继续退到next[j]的位 置,也就是ne[ne[j]],这样循环直到最后。

最后, 我写的也不是很好,有很多想说的也没有完全表达出来,但是我尽力将我对KMP算法的知识点总结了下来,自己对KMP也有了一个更深刻的印象,希望对读者也有所帮助,如果对next数组还不太懂的可以参考一下这个视频「天勤公开课」KMP算法易懂版_哔哩哔哩_bilibili

这个视频我感觉还是比较浅显易懂的,而且还有动画演示,讲的很容易听懂。 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

KMP算法详解 的相关文章

随机推荐

  • 使用jQuery实现返回顶部功能

  • JS 树(数组存储)进行递归遍历获取路径

    JS 树 数组存储 进行递归遍历获取路径 实现功能 通过叶子节点 id 寻找包含该叶子节点的整条路径 树的数据以数组形式保存 直接上代码 const getPathByKey curKey data gt let result 记录路径结果
  • python 使用sphinx 快速生成说明文档

    目录 python 使用sphinx 快速生成说明文档 1 安装sphinx 2 文件结构 3 修改配置文件 4 生成html文档 生成markdown文档 1 安装依赖 2 修改配置文件 3 生成markdown文档 python 使用s
  • 矩阵论—凯莱-哈密顿定理

    凯莱 哈密顿定理内容 凯莱 哈密顿定理典型例题 典型例题 我们先来观察这个题目 题目要求 若直接将矩阵A 代入计算 则会非常复杂 因此 这条路是走不通的 我们试着引入我们今天介绍的凯莱 哈密顿定理来解这个题目 令 我们要求 即求即可 接下来
  • C++ 友元

    友元一般存在于不同类之间 在一个类中 可以用全局函数作友元函数 而在不同类中 类成员函数作友元函数 友元可以是一个函数 该函数被称为友元函数 函数既可以是全局也可以是类的成员 友元也可以是一个类 该类被称为友元类 同类对象间无私处 异类对象
  • C语言实现惯导系统的间接粗对准

    C语言实现惯导系统的间接粗对准 惯导系统是一种常见的导航系统 用于测量和跟踪飞行器的位置 速度和方向 其中的粗对准是指通过传感器测量的数据进行校准 以提高系统的准确性和稳定性 本文将介绍如何使用C语言实现惯导系统的间接粗对准算法 并提供相应
  • json文件解析出现异常

    今天在尝试用自带的NSJSONSerialization方法来解析本地json文件的时候碰到了系统异常 app自动终止 问题如下 代码
  • [机器学习与scikit-learn-32]:算法-回归-普通线性模型拟合非线性分布数据-分箱

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 123562666 目录 前言 第1章
  • 求兔子繁殖后的数量?

    有一对兔子 从出生后第3个月起每个月都生一对兔子 小兔子长到第三个月后每个月又生一对兔子 假如兔子都不死 问每 个月的兔子总数为多少 程序分析 兔子的规律为数列1 1 2 3 5 8 13 21 include
  • 暑期作息时间表模板_人民日报给孩子的暑假作息时间表,太详细!太及时了!(可打印)...

    希望每天都收到我们的文章吗 点上面蓝色文 语文日刊 关注就可以了 2019年8月高考优秀作文专辑8月出炉 买买买 高考第一品牌语文月刊代码46 88每月一本定价12元 其中每年8月高考优秀作文点评专辑 9月高考试题分析专辑 12月最新高考分
  • vue配置vue.config.js

    现在的 vue config js const defineConfig require vue cli service module exports defineConfig transpileDependencies true 关闭es
  • Visual Studio 2019 从依赖包开始手动编译opencv

    windows opencv compile document 本文主要是教你如何从源码编译软件包 建议你通过vcpkg安装完整版本的OpenCV4 含gpu功能 来安装使用 1 依赖项目编译安装 在开始之前必须先安装vcpkg 1 1 准
  • [C/C++]基础 %md,%0md是什么意思

    1 d就是普通的整型输出 2 2d是将数字按宽度为2 采用右对齐方式输出 若数据位数不到2位 则左边补空格 3 02d和 2d差不多 只不过是左边补0 修饰符 格式说明 意义 1 m md 以宽度为m输出整型数 输出不足m位时 左补空格 2
  • 首发

    译者 Linstancy 责编 Jane 出品 AI科技大本营 公众号id rgznai100 回顾 CVPR 2018 旷视科技有 8 篇论文被收录 如高效的移动端卷积神经网络 ShuffleNet 语义分割的判别特征网络 DFN 优化解
  • 我的世界java版如何看坐标_我的世界中怎么查看坐标,坐标系统详解

    本篇教程将通过图文的形式一步步教你在我的世界中查看理解坐标系统 XYZ 坐标系统解释 我的世界地图有XYZ3个坐标 通过XYZ来显示你所处地图的区域 下面是每个坐标的详解 X 显示你在地图上的 东 西 位置 正数表示东 负数表示西 Y 显示
  • 什么是域名? 什么是DNS?

    域名 关于域名 百度百科是这样介绍的 百度百科 https baike baidu com item E5 9F 9F E5 90 8D 86062 域名 英语 Domain Name 又称网域 是由一串用点分隔的名字组成的Internet
  • 深入理解数据结构—简单链表

    一 简单链表结构 include
  • python异步requests_Python asyncio requests 异步爬虫

    python asyncio requests async await crawler 一 情景 抓取大量URL 每个URL内信息量较少 任务清单 发送URL请求N次 接受并处理URL响应N次 二 分析 如果每个页面依次抓取的话 任务流程
  • Unity脚本设置Animator单个状态的speed

    Unity脚本设置Animator单个状态的speed 直接上代码 private Animator anim private AnimatorController animController private void Awake ani
  • KMP算法详解

    什么是KMP算法 有句话可以这么形容KMP 一个人能走的多远不在于他在顺境时能走的多快 而在于 他在逆境时多久能找到曾经的自己 KMP算法是一个字符串匹配算法 取得是三个发明人的名字首字母 KMP算法的作用 是在一个已知字符串中查找子串的位