【夜深人静学数据结构与算法 | 第一篇】KMP算法

这篇具有很好参考价值的文章主要介绍了【夜深人静学数据结构与算法 | 第一篇】KMP算法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

 前言:

 KMP算法简介:

引入概念:

前缀后缀

前缀表:

简单例子:

暴力遍历:

KMP算法:​

 KMP算法难点:

总结:


 前言:

本篇我们将详细的从理论层面介绍一下什么是KMP算法,相对应的力扣刷题专栏里也会有相对应的习题,欢迎各位前往阅读。

 KMP算法简介:

         KMP算法是一种字符串匹配算法用于在一个文本串中查找某个子串出现的位置。KMP算法的原理是根据模式串的特点,在匹配过程中避免重复匹配已经匹配过的部分。具体来说,KMP算法维护两个指针:i和j,表示当前匹配位置和模式串匹配的起点。当出现不匹配时,通过已匹配部分构建一个next数组,用以确定模式串下一次匹配起点的位置。

        KMP算法的时间复杂度为O(n+m),其中n为文本串长度,m为模式串长度。KMP算法应用广泛,例如在文件查找、模糊查询等领域都有广泛的应用。

KMP算法的本质就是跳过一部分暴力循环下的无效比较,达到节省时间复杂度的目的

引入概念:

前缀后缀

前缀:字符串中包含首字母但是不包含尾字符的所有子串

后缀:字符串中包含尾字母但是不包含首字符的所有子串

【夜深人静学数据结构与算法 | 第一篇】KMP算法

举例:

字符串aabaaf的前缀后缀分别有:

前缀 后缀
a a

aa

aa
aab baa
aaba abaa
aabaa abaaf

前缀表:

一个字符串中每一个子串都有自己的前缀和后缀,也就都有自己的最长相等前后缀长度,这些长度组成的一个数组,我们把它叫做前缀表

举例:
字符串aabaaf的前缀表:

子串 前缀 后缀 最长相等前后缀长度
a 0
aa a a 1
aab a,aa b,ab 0
aaba a,aa,aab a,ba,aba 1
aabaa a,aa,aab,aaba a,aa,baa,abaa 2
aabaaf a,aa,aab,aaba,aabaa f,af,aaf,baaf,abaaf 0

简单例子:

假设我们要在父串aabaabaaf中寻找子串aabaaf

暴力遍历:

正常情况是我们在父串中逐一遍历,父串与子串挨个匹配,直到找到与子串完全一致的为止:
 【夜深人静学数据结构与算法 | 第一篇】KMP算法

错误之后重新比较:
 【夜深人静学数据结构与算法 | 第一篇】KMP算法

 最终:

【夜深人静学数据结构与算法 | 第一篇】KMP算法

我们可以发现暴力遍历之所以时间复杂度高,是因为只要出错,父指针与子指针就会不断的回溯。第一次出错后,父类指针回溯到1,子类指针回溯到0,重新开始比较,第二次出错父类指针回溯到2,子类指针回溯到0,重新开始比较。如此类推。大量的回溯带来了高昂的时间成本,我们就在想如何才能够精简回溯,于是经过不懈努力,我们创造出了KPM算法。

   KMP算法:
【夜深人静学数据结构与算法 | 第一篇】KMP算法

KMP算法采取了自定义i和j的回溯,通过控制i和j的回溯位置来降低回溯的时间成本。

【夜深人静学数据结构与算法 | 第一篇】KMP算法

 此时按照暴力遍历的思路,我们是让i等于1,j等于0重新开始第二轮遍历,但是我们KMP算法给出了新的思路:

我们不对已经比较过的字符串进行二次比较,就节省了回溯成本,既然绿色前缀和红色后缀相等,都是aa,那么下一次我们让子类的绿色前缀对齐父类的红色后缀,这样我们就不用回溯i和j,也可以开启新一轮的字符串对比

为什么可以这样操作呢?

前提是我们已经知道前面字符串只有b和f不匹配,其他的一切都是匹配的,那么此时往前回溯字符串

【夜深人静学数据结构与算法 | 第一篇】KMP算法

 找到两串最长的相等的一个包含首字母的字符串(前缀),一个包含尾字母的字符串(后缀),因为二者是相等的,那么我们就可以得到ABCD四个子串:

那其实我们这里不移动最长的也可以,只不过移动最长的会简化的更多而已。

【夜深人静学数据结构与算法 | 第一篇】KMP算法

那么既然B和D匹配,D又和C匹配,也就是我们在下一次循环的时候,可以直接让B对齐C,而BC又是匹配的,我们不用重新对其进行匹配,可以直接从此处的i和j开始。

【夜深人静学数据结构与算法 | 第一篇】KMP算法 而不断的循环这种比较方法,直至找到父类中符合要求的子串,就实现了KMP算法下的字符串匹配。

 

通过这个我们可以总结出来模板:

        每一次我们都找到不匹配字母前面的字符串(例如我们这里aabaaff不匹配,前置字符串就是aabaa)然后找出他的最长相等前缀后缀长度(找出有无符合上述这种红绿组合的),这里的最长相等前缀后缀是2,最后我们让i不回溯,j回退到最长相等前缀后缀位置开始向后匹配。

    而next数组其实就是我们为了方便使用不匹配字母前置的字符串的最长相等前缀后缀长度,我们自己进行提前算出这个字符串的所有子串的最长相等前缀后缀长度,存储在一个数组里面方便直接使用,我们给这个把这个数组叫做next数组

     需要注意的是next数组的版本多种多样,通常会对里面的数据进行各种处理。不过本质存放的都是前缀表,因此我们其实可以不对next数组做任何处理,就让他存放前缀表,依然可以实现KMP算法。

以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!

下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面从新匹配就可以了。

所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。

next表的种类:

我们还是以aabaaf举例:
【夜深人静学数据结构与算法 | 第一篇】KMP算法

 KMP算法难点:

整个KMP算法都可以看作两部分

  • 内核:前缀表的求解,建立next数组
  • 外壳:利用前缀表控制子字符串的回溯

主要的难点在于:如何求字符串的前缀表 

其实字符串的前缀表数组计算本质上也是在运用KMP算法。

它是把字符串的前缀当作了模式串,把字符的后缀当成了文本串

void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
                j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }

KMP算法完整版:

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while(j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

总结:

KMP算法的优点主要包括以下几点:

1. 高效率:KMP算法的时间复杂度为O(n+m),其中n是文本串长度,m是模式串长度,相比暴力匹配算法的时间复杂度O(nm)有很大的提升。

2. 可扩展性:KMP算法不要求文本和模式串的长度一致,因此能够有效地处理不同长度的字符串匹配问题。此外,KMP算法还可以支持多模式匹配,即在一个文本串中查找多个模式串。

3. 避免重复匹配:KMP算法通过计算模式串的next数组来避免重复匹配已经匹配过的部分,从而提高了匹配效率。

4. 空间复杂度低:KMP算法只需要一个长度为m的next数组来存储模式串中最长相同前后缀的长度,空间复杂度相对较低。

5. 实现简单:KMP算法的实现相对简单,易于理解和使用。

总之,KMP算法是一种高效、可扩展、避免重复匹配、空间复杂度低、实现简单的字符串匹配算法,对于字符串匹配问题具有广泛的应用价值。

今天的内容到这里就结束了,感谢大家的阅读。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

 【夜深人静学数据结构与算法 | 第一篇】KMP算法文章来源地址https://www.toymoban.com/news/detail-479713.html

到了这里,关于【夜深人静学数据结构与算法 | 第一篇】KMP算法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【夜深人静学数据结构与算法 | 第十篇】动态规划

    目录 前言: 动态规划: 常见应用: 解题步骤:  动态规划的简化步骤: 案例: 509. 斐波那契数 - 力扣(LeetCode) 70. 爬楼梯 - 力扣(LeetCode) 62. 不同路径 - 力扣(LeetCode) 总结:         本文我们将为大家讲解一下动态规划的理论知识,并且会讲解几道力扣的经典例题。

    2024年02月11日
    浏览(47)
  • 【夜深人静学数据结构与算法 | 第九篇】栈与队列

    目录 ​前言: 栈: 栈的实际应用:  队列: 队列的实际应用: 总结:         栈与队列是我们学习的两个经典的数据结构,这两个数据结构应用广泛,在计算机内有很多底层应用,而很多算法也是依靠栈和队列来实现的,因此我们要想学好数据结构与算法,就要学好栈与

    2024年02月15日
    浏览(35)
  • 【夜深人静学习数据结构与算法 | 第十二篇】动态规划——背包问题

      目录  前言:  01背包问题: 二维数组思路: 一维数组思路: 总结:       在前面我们学习动态规划理论知识的时候,我就讲过要介绍一下背包问题,那么今天我们就来讲解一下背包问题。 在这里我们只介绍 01背包 ,至于分组背包和混合背包这种的已经属于竞赛级别的

    2024年02月12日
    浏览(37)
  • 【夜深人静学数据结构与算法 | 第二篇】后缀(逆波兰)表达式

    目录 前言:  中缀表达式:  后缀表达式: 中缀表达式转后缀表达式: 后缀表达式计算结果: 总结:  计算机在计算四则运算的时候,由于括号以及运算优先级的存在,并不能够很好的处理所有的运算,为了处理这种情况,我们引入了后缀表达式来优化算法。         

    2024年02月13日
    浏览(40)
  • 【夜深人静学数据结构与算法 | 第四篇】手撕二叉树遍历

    目录 前言: 二叉树遍历方式: 手撕前中后序遍历(递归)的三大准备 深度优先搜索:  手撕前中后遍历(递归): 手撕前中后序遍历(迭代): 深度优先搜索: 总结:         今天我们将带领大家手撕二叉树的遍历,本篇会分别讲解深度优先搜索法和广度优先有搜索法下

    2024年02月09日
    浏览(34)
  • 【夜深人静学数据结构与算法 | 第七篇】时间复杂度与空间复杂度

    前言:  引入:  时间复杂度:  案例: 空间复杂度: 案例: TIPS:        总结:         今天我们将来介绍时间复杂度和空间复杂度,我们代码的优劣就是依靠这个在评判,以此为背景,我们诞生出了不少的经典思路:用时间换空间,用空间换取时间。而大多数同学

    2024年02月10日
    浏览(53)
  • 夜深人静学32系列10——GPIO中断/NVIC/EXTI/SYSCFG详解,外部中断控制LED

    上期我们学习了GPIO驱动数码管/蜂鸣器/LED和按键等外设,本期我们一起来学习STM32中断的相关内容 当CPU正在处理某个事件的时候,外界发生了紧急事件请求,CPU需要暂停当前的工作,转而去处理这个紧急事件,处理完之后,再次回到之前被中断的地方,继续执行原来的工作,

    2024年01月16日
    浏览(34)
  • 算法竞赛:初级算法(第一章:基础数据结构)

    动态链表 动态链表需要 临时分配链表节点 ,使用完毕后释放。 优点 :能及时释放空间,不使用多余内存 缺点 :需要管理空间,容易出错(竞赛一般不用动态链表) 静态链表 静态链表使用 预先分配的一段连续空间 存储链表,这种链表在逻辑上是成立的。 有两种做法:

    2024年01月19日
    浏览(34)
  • 数据结构英文习题解析-第一章 算法复杂度分析Algorithm Analysis

    前言:最近快到FDS考试了,po重刷了一下学校的题目,自己整理了一些解析orz 因为po在自己找解析和学习的过程中非常痛苦,所以在此共享一下我的题目和自己写的解题思路,欢迎各位指出错误~全章节预计会陆续更新,可在专栏查看~ HW1 1. The major task of algorithm analysis is to an

    2024年03月12日
    浏览(53)
  • 第一百零五天学习记录:数据结构与算法基础:顺序表(王卓教学视频)

    注:笔记截图均来自王卓数据结构教学视频 线性表是具有相同特性的数据元素的一个有限序列 同一线性表中的元素必定具有相同特性,数据元素间的关系是线性关系。 稀疏多项式的运算 顺序存储结构存在的问题 1、存储空间分配不灵活 2、运算的空间复杂度高 引出链式存储

    2024年02月15日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包