首页 > 编程语言 >字符串匹配之KMP算法中的pnext表

字符串匹配之KMP算法中的pnext表

时间:2023-03-12 12:22:24浏览次数:50  
标签:p0 匹配 len 算法 KMP pk pi pnext

pnext表的分析

上篇我们提到了最后是构建一个pnext表,记录着每个字符匹配需要移动的长度的位置信息,接着上篇的内容,我们来分析下pnext表的构造。

还是举个栗子:

ababcabcacbab
  abcac  # 状态1
     abcac  # 状态2

如状态1所示,这时位置i的字符是最后一个c,对应的位置ki的字符应该是b。由此可知:

  • 模式串移动之后,作为下一个用于匹配的字符串的新位置,其前缀子串应该与匹配失败的字符串之前同样长度的子串相同。
  • 如果匹配在模式串的位置i失败时,而位置i的前缀子串中满足杉树条件的位置不止一处,那么只能做最短的移动,将模式串移动到最近的满足上述条件的位置,保证不遗漏可能的匹配。

现在考虑pnext表的构造问题,首先已知ki的值只依赖模式串本身的前i的字符。在下面的分析和讨论中参考下图。

图一

 

 

 首先,如图1状态(1)所示,目标串中位置j之前的i个字符,也就是模式串的前i个字符,也就是说,目标串中的子串tj-i…ti-1就是p0…pi-1。

现在需要找一个位置k,下次匹配用pk与前面匹配失败的tj比较,也就是把模式串移动到pk与tj对准的位置,也即状态(2),如果移动的正确,那么模式串里的子串p0…pk-1应该与子串的pi-k…pi-1匹配,这两个子串分别是p0…pi-1的长度为k的前缀和后缀,这样确定k的问题就变成了确定p0…pi-1的相等前缀和和后缀的长度。显然有k越小移动越远的结论。前面说的移动距离尽可能短。与之对应的是应该找的k是p0…pi-1的最长的相等前缀和后缀的长度,这样才能保证不会跳过可能的匹配。

上图中可以看出,如果p0…pi-1的最长相等前后缀的长度为k(0<k<i-1),在pi≠tj的时候,模式串就应该右移i-k位。也就是说,应该把pnext[i]设置为k。

KMP的巧妙的递推算法

 

图二

 

现在假设对子串p0…pi-1pi(也就是相对位置i)递推最长相等前后缀的长度,这时对pnext[i-1]已经计算出结果为k-1,比较pi与pk,有两种情况:

  1. 如果pi=pk,那么对于i的最长相等前后缀的长度,比对i-1的最长相等前后缀的长度多1,此时应该将pnext[i]设置为k,然后考虑下一个字符。
  2. 把前i-1个子串的最前相等前缀移动过来继续检查。

第二种情况并没有设置,只是继续检查。不难确定,移动过来检查是正确的。

已知pnext[0] = -1和直至pnext[i-1]的已有值求pnext[i]的算法:

  1. 假设pnext[i-1] = k-1,如果pi=pk,那么最长前后缀长度就是k,将其计入pnext[i],将i值加一后继续递推(循环)。
  2. 如果pi≠pk,就将k设置为pnext[k]的值,(将k设置为pnext[k],也就是转去考虑前一个更短的保证匹配的前缀,可以基于它继续检查)。
  3. 如果k的值为-1,(这个值一定是来源于步骤2),那么p0…pi的最长相同前后缀的长度就是0,设置pnext[i]=0,将i值加一后继续递推。

pnext表构造算法

基于上面的分析定义出来的算法:

def gen_next(p):
    i, k, pattern_len = 0, -1, len(p)
    pnext = [-1] * pattern_len  # 初始化数组元素全部为-1
    while i < pattern_len - 1:  # 生成下一个pnext元素值
        if k == -1 or p[i] == p[k]:
            i, k = i + 1, k + 1
            pnext[i] = k  # 设置pnext元素
        else:
            k = pnext[k]  # 退到更短相同前缀
    return pnext

  函数刚开始时建立一个元素值全部为-1的表,循环中为下标为0之后的各元素赋值,具体处理完全是上面分析的情况,该算法的形式与前面KMP串匹配的主函数极为类似,算法分析情况可以照搬,结论是算法时间复杂度是O(m),m是模式串的长度。

算法改进

参考图一的状态1和状态2,由于匹配失败时有pi≠tj,设pnext[i]=k,如果发现pi=pk,那么也就一定有pk≠tj。所以这种情况下,实际模式串应该是右移到pnext[k](而不是仅右移到pnext[i]),下一步应该用pnext[k]与tj比较,这一修改可以把模式串移动的更远,可能会提高效率。修改后的函数定义如下:

def gen_next(p):
    i, k, pattern_len = 0, -1, len(p)
    pnext = [-1] * pattern_len  # 初始化数组元素全部为-1
    while i < pattern_len - 1:  # 生成下一个pnext元素值
        if k == -1 or p[i] == p[k]:
            i, k = i + 1, k + 1
            if p[i] == p[k]:
                pnext[i] = pnext[k]
            else:
                pnext[i] = k  # 设置pnext元素
        else:
            k = pnext[k]  # 退到更短相同前缀
    return pnext

  许多场景中需要用一个模式串反复在一个或者多个目标串里匹配,这种情况下,构造模式串里的工作只需要做一次,后面匹配中只需要简单反复使用,就是最适合KMP算法的场景。

标签:p0,匹配,len,算法,KMP,pk,pi,pnext
From: https://www.cnblogs.com/f-g-f/p/17207960.html

相关文章

  • Qz学算法-数据结构篇(排序算法--冒泡、选择)
    排序算法排序的概念排序也称排序算法(SortAlgorithm),排序是将一组数据,依指定的顺序进行排列的过程分类排序的分类:内部排序:指将需要处理的所有数据都加载到内部存储器中进......
  • m通信系统中基于相关峰检测的信号定时同步算法的FPGA实现
    1.算法描述       定时同步方法主要分为基于数据辅助和非数据辅助两类。前者是在发送有效数据前发送一段具有某种特征的训练或导频符号,接收端根据符号特征建立同步......
  • 数据结构与算法2
    树的术语及定义          实现  节点与引用,程序         ......
  • 对排序算法稳定性的理解
    之前上课提到排序算法的稳定性,知道大体是个什么意思,但是具体的意义依旧不清楚,因此记录一下。定义 排序之后让相同的值维持相同的次序意义 与具体需求有关,如果只是单纯......
  • 算法探索_缺失的第一个正数
    问题描述:给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。示例 1:输入:[1,2,0]输出:3示例 2:输入:[3,4,-1,1]输出:2示例 3:输入:[7,8,9,11,12]输......
  • 算法探索_搜索旋转排序数组
    问题描述:假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个......
  • 系统架构设计师考试知识点整理-4:死锁问题、银行家算法、管程与线程
    死锁问题1.死锁是指多个进程之间相互等待对方的资源,而在得到对方资源之前又不释放自己的资源所造成的循环等待的现象。2.死锁产生的根本原因在于系统提供的资源少于并发进程......
  • 每日算法 230311
    题目面试题17.05.字母与数字难度中等153给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。返回该子数组,若存在多个最长子数组,返回左端......
  • 线性回归算法
    1.算法原理y=w*x+b+εloss=Σ(w*xi+b-yi)2w'=w-lr*(loss对w求偏导)      #梯度下降算法b'=b-lr*(loss对b求偏导)       #梯度下降算法 2.......
  • 避免死锁(银行家算法)
    避免死锁(银行家算法)1、什么是安全序列2、安全序列、不安全状态、死锁的联系3、银行家算法实现思想知识回顾......