首页 > 编程语言 >KMP字符串匹配算法

KMP字符串匹配算法

时间:2024-02-23 18:46:16浏览次数:20  
标签:匹配 前缀 后缀 算法 KMP 字符串 长度 文本

什么是KMP

KMP算法主要应用在字符串匹配问题。 因为是由这三位学者发明的:Knuth,Morris和Pratt,所以取了三位学者名字的首字母。所以叫做KMP  

核心思想

KMP的主要思想是: 「当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。」 (可以看出有记忆化搜索的思想) 所以如何记录已经匹配的文本内容,是KMP的重点,也是next数组肩负的重任。  

一、前缀表:next数组

next数组就是一个前缀表(prefix table)。 「前缀表是用来回溯的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。」 [ 前缀表存的是:模式串已匹配范围内,最长相同前后缀长度 ] 记忆化搜索思想 + 找当前匹配范围内 最长相同前、后缀   为了清楚的了解前缀表的来历,我们来举一个例子: 要在文本串:aabaabaafa中查找是否出现过一个模式串:aabaaf。 请记住文本串和模式串的作用,对于理解下文很重要,要不然容易看懵。所以说三遍: 要在文本串:aabaabaafa中查找是否出现过一个模式串:aabaaf。 要在文本串:aabaabaafa中查找是否出现过一个模式串:aabaaf。 要在文本串:aabaabaafa中查找是否出现过一个模式串:aabaaf。 动画里,我特意把 子串aa 标记上了,这是有原因的,大家先注意一下,后面还会说道。 可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,会发现不匹配,此时就要从头匹配了。 但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。   此时就要问了「前缀表是如何记录的呢?」 首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,在重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。 那么什么是前缀表:「下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。」  

为什么一定要用前缀表

这就是前缀表那为啥就能告诉我们 上次匹配的位置,并跳过去呢? 回顾一下,刚刚匹配的过程在下表5的地方遇到不匹配,模式串是指向f,如图: 然后就找到了下表2,指向b,继续匹配:如图: 以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要! 「下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。」 所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。 「很多介绍KMP的文章或者视频并没有把为什么要用前缀表?这个问题说清楚,而是直接默认使用前缀表。」

如何计算前缀表

接下来就要说一说怎么计算前缀表。 长度为前1个字符的子串a最长相同前后缀的长度为0。(注意这里计算相同前后缀,不算重复的字符 长度为前2个字符的子串aa,最长相同前后缀的长度为1。(前缀a,后缀a) 长度为前3个字符的子串aab,最长相同前后缀的长度为0。   以此类推: 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。 那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图: 可以看出「前缀表里的数值代表着就是:当前位置之前的子串有多大长度相同的前缀后缀。」   再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示: 找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。 为什么要看前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。 所以要看前一位的 前缀表的数值。 前一个字符的前缀表的数值是2, 所有把下表移动到下表2的位置继续比配。可以再反复看一下上面的动画。 最后就在文本串中找到了和模式串匹配的子串了。

前缀表有什么问题

来看一下刚刚求的这个前缀表有什么问题呢? 如图: 看这个位置红框的位置,如果要找下表1 所对应 前缀表里的数值的时候,前缀表里的数值依然是1,然后就要跳到下表1的位置,如此就「形成了一个死循环」「如何怎么避免呢,就把前缀表里的数值统一减一, 开始位置设置为-1 。」 这一点对理解后面KMP代码很重要!! 改为如图所示: 这样就避免的死循环,只不过后续取 前缀表里的数值的时候,要记得再+1,才是我们想要的值。 「最后得到的新前缀表在KMP算法里通常用一个next数组来表示。」 注意这个next数组就根据模式串求取的。

使用next数组来匹配

有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。 注意next数组是新前缀表(旧前缀表统一减一了)。 匹配过程动画如下:

时间复杂度分析

KMP算法能在字符串匹配时,让文本串指针不回头! 再来看一下时间复杂度, 假设文本串长度为n,模式串长度为m,动画为上图。 其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n)(文本串上指针不回头),但之前还要单独生成next数组,时间复杂度是O(m)(next数组的实现代码将在后续文章中继续讲解),所以整个KMP算法的时间复杂度是O(n+m)的。 暴力的解法显而易见是O(n * m),所以「KMP在字符串匹配中极大的提高的搜索的效率。」  

标签:匹配,前缀,后缀,算法,KMP,字符串,长度,文本
From: https://www.cnblogs.com/jk-2048/p/18030196

相关文章

  • 递归与回溯算法
    递归函数中自己调用自己经典例题:汉诺塔需要将所有盘子按顺序放到塔C上(问题规模:n)就需要最大的盘子在C底部就需要将其余所有盘子移动到塔B上 第二塔上也需要按顺序摆放(问题规模:n-1)就需要第二大的盘子在B底部就需要将其余所有盘子移动到另一个塔上··········......
  • 贪心算法思想
    贪心算法每一步都找到当前局部最优解,短视,但是有思考贪心算法(GreedyAlogorithm)又叫登山算法,它的根本思想是逐步到达山顶,即逐步获得最优解,是解决最优化问题时的一种简单但是适用范围有限的策略。贪心算法没有固定的框架,算法设计的关键是贪婪策略的选择。每一步都找到最优解,由......
  • 单调栈算法
     定义栈内元素单调按照递增(递减)顺序排列的栈。主要用于找到每一个元素左边或右边,第一个比它大或小的数时,可使用单调栈算法。时间复杂度由于每个元素最多各自进出栈一次,复杂度是O(n)功能递增单调栈:在一个队列中针对每一个元素从它右边找到第一个比它小的元素在一个......
  • 排序算法-归并排序
    时空复杂度时间复杂度:O(nlogn)空间复杂度:O(n)使用了分治思想优势1.稳定归并的时空复杂度非常稳定的,不论是在哪种情况下,归并算法的时间复杂度都不变,2.高效归并算法计算效率相比其他算法也是非常快的 思路图解分把一个有n个元素的数组,分成n个有1个元素的数组然后边比较......
  • [几何算法]任意多边形求面积
    求任意平面多边形的面积通过鞋带定理,在已知多边形各顶点的情况下,可以快速计算出其面积问题分析设一个多边形顶点按逆时针或顺时针顺序为$$P_1(x_1,y_1),P_2(x_2,y_2),\ldots,P_n(x_n,y_n)$$,其中$$P_1=P_{n+1}$$(首尾相连形成闭合多边形)。根据鞋带定理,该多边形的......
  • 基于yolov2深度学习网络的车辆行人检测算法matlab仿真
    1.算法运行效果图预览   2.算法运行软件版本MATLAB2022a 3.算法理论概述      近年来,深度学习在计算机视觉领域取得了显著成果,特别是在目标检测任务中。YOLO(YouOnlyLookOnce)系列算法作为其中的代表,以其高效和实时的性能受到广泛关注。YOLOv2,作为YOL......
  • 基于WIFI指纹的室内定位算法matlab仿真
    1.算法运行效果图预览  2.算法运行软件版本matlab2022a 3.算法理论概述        随着移动互联网和物联网技术的飞速发展,位置服务(LBS)已成为许多应用的核心功能,如导航、社交网络和智能物流等。室外定位技术,如全球定位系统(GPS),已相当成熟并广泛应用。然而,由于建......
  • 类欧几里得算法
    要求类似于这样的东西:\[\begin{align*}f(n,a,b,c)&=\sum\limits_{i=0}^n{\left\lfloor\frac{ai+b}{c}\right\rfloor}\\g(n,a,b,c)&=\sum\limits_{i=0}^n{\left\lfloor\frac{ai+b}{c}\right\rfloor}^2\\h(n,a,b,c)&=\sum\limits_{i=0}^......
  • day41 动态规划part3 代码随想录算法训练营 96. 不同的二叉搜索树
    题目:96.不同的二叉搜索树我的感悟:这题,考的概率不大,听一遍,过一遍就行。理解难点:二叉搜索树定义为什么是累加的听课笔记:代码示例:classSolution:defnumTrees(self,n:int)->int:dp=[0]*(n+1)#创建一个长度为n+1的数组,初始化为0d......
  • day40 动态规划part3 代码随想录算法训练营 343. 整数拆分
    题目:343.整数拆分我的感悟:题目很难,但我动力十足!!理解难点:如何拆分为什么要保留dp[i]听课笔记:代码示例:classSolution:defintegerBreak(self,n:int)->int:#思路:#dp[i]是到目前为止能拆分取的最大值#dp[i]可以拆成j*(集合)......