zoukankan      html  css  js  c++  java
  • kmp的感想

    字符串匹配

    转载,作者洛谷网校 阮行止
    https://www.zhihu.com/question/21923021/answer/1032665486

    前导

    这里用到的数组下标都是从0开始的

    1.思想

    最朴素的算法就是一个一个地进行比较,但是有一种算法,它将信息运用到了极致,**它就是kmp

    它最核心的思想就是尽可能利用残余的信息。**

    2.优化

    在** Brute-Force **中,如果从 S[i] 开始的那一趟比较失败了,算法会直接开始尝试从 S[i+1] 开始比

    较。这种行为,属于典型的“没有从之前的错误中学到东西”。我们应当注意到,一次失败的匹配,会给

    我们提供宝贵的信息——如果 S[i : i+len(P)] 与 P 的匹配是在第 r 个位置失败的,那么从 S[i] 开始的

    (r-1) 个连续字符,一定与 P 的前 (r-1) 个字符一模一样!

    3.跳过不可能成功的字符串比较

     
    有些趟字符串比较是有可能会成功的;有些则毫无可能。我们刚刚提到过,优化 Brute-Force 的路线是“尽

    量减少比较的趟数”,而如果我们跳过那些绝不可能成功的字符串比较,则可以希望复杂度降低到能接受的范

    围。

    首先,利用上一节的结论。既然是在 P[5] 失配的,那么说明 S[0:5] 等于 P[0:5],即"abcab". 现在我

    们来考虑:从 S[1]、S[2]、S[3] 开始的匹配尝试,有没有可能成功?  从 S[1] 开始肯定没办法成功,

    因为 S[1] = P[1] = 'b',和 P[0] 并不相等。从 S[2] 开始也是没戏的,因为 S[2] = P[2] = 'c',

    并不等于P[0]. 但是从 S[3] 开始是有可能成功的——至少按照已知的信息,我们推不出矛盾。

    4.带着“跳过不可能成功的尝试”的思想,我们来看next数组。

    next数组是对于模式串而言的,是要进行匹配的。P 的 next 数组定义为:next[i] 表示 P[0] ~

    P[i] 这一个子串,使得 前k个字符恰等于后k个字符 的最大的k. 特别地,k不能取i+1(因为这个子串一共

    才 i+1 个字符,自己肯定与自己相等,就没有意义了)。

    上图给出了一个例子。P="abcabd"时,next[4]=2,这是因为P[0] ~ P[4] 这个子串是"abcab",前两个字

    符与后两个字符相等,因此next[4]取2. 而next[5]=0,是因为"abcabd"找不到前缀与后缀相同,因此只能

    取0.   如果把模式串视为一把标尺,在主串上移动,那么 Brute-Force 就是每次失配之后只右移一位;

    改进算法则是每次失配之后,移很多位,跳过那些不可能匹配成功的位置。但是该如何确定要移多少位呢?

    在 S[0] 尝试匹配,失配于 S[3] <=> P[3] 之后,我们直接把模式串往右移了两位,让 S[3] 对准 P[1].

    接着继续匹配,失配于 S[8] <=> P[6], 接下来我们把 P 往右平移了三位,把 S[8] 对准 P[3]. 此后继

    续匹配直到成功。  我们应该如何移动这把标尺?很明显,如图中蓝色箭头所示,旧的后缀要与新的前缀一

    致(如果不一致,那就肯定没法匹配上了)!  回忆next数组的性质:P[0] 到 P[i] 这一段子串中,前

    next[i]个字符与后next[i]个字符一模一样。既然如此,如果失配在 P[r], 那么P[0]~P[r-1]这一段里

    面,前next[r-1]个字符恰好和后next[r-1]个字符相等——也就是说,我们可以拿长度为 next[r-1] 的那一

    段前缀,来顶替当前后缀的位置,让匹配继续下去!  您可以验证一下上面的匹配例子:P[3]失配后,把

    P[next[3-1]]也就是P[1]对准了主串刚刚失配的那一位;P[6]失配后,把P[next[6-1]]也就是P[3]对准了

    主串刚刚失配的那一位。

    如上图所示,绿色部分是成功匹配,失配于红色部分。深绿色手绘线条标出了相等的前缀和后缀,其长度为

    next[右端]. 由于手绘线条部分的字符是一样的,所以直接把前面那条移到后面那条的位置。因此说,next

    数组为我们如何移动标尺提供了依据。接下来,我们实现这个优化的算法。

    利用next数组进行匹配

    了解了利用next数组加速字符串匹配的原理,我们接下来代码实现之。分为两个部分:建立next数组、

    利用next数组进行匹配。

    首先是建立next数组。我们暂且用最朴素的做法,以后再回来优化:

    void calc_next()
    {
    next[1]=0;
    for(int i=2,j=0;i<=n;i++)
      {
       while(j>0&&a[i]!=a[j+1])
           j=next[j];
        if(a[i]==a[j+1])
         j++,next[j]=j;
       }
    }
    

    快速求next数组  终于来到了我们最后一个问题——如何快速构建next数组。  首先说一句:快速构建

    next数组,是KMP算法的精髓所在,核心思想是“P自己与自己做匹配”。  为什么这样说呢?回顾next数组

    的完整定义:定义 “k-前缀” 为一个字符串的前 k 个字符; “k-后缀” 为一个字符串的后 k 个字符。k 必

    须小于字符串长度。 next[x] 定义为: P[0]~P[x] 这一段字符串,使得k-前缀恰等于k-后缀的最大的

    k.  这个定义中,不知不觉地就包含了一个匹配——前缀和后缀相等。接下来,我们考虑采用递推的方式求出

    next数组。如果next[0], next[1], ... next[x-1]均已知,那么如何求出 next[x] 呢?  来分情况讨

    论。首先,已经知道了 next[x-1](以下记为now),如果 P[x] 与 P[now] 一样,那最长相等前后缀的长

    度就可以扩展一位,很明显 next[x] = now + 1. 图示如下。

    刚刚解决了 P[x] = P[now] 的情况。那如果 P[x] 与 P[now] 不一样,又该怎么办?

    如图。长度为 now 的子串 A 和子串 B 是 P[0]~P[x-1] 中最长的公共前后缀。可惜 A 右边的字符和 B 右边的那个字符不相等,next[x]不能改成 now+1 了。因此,我们应该缩短这个now,把它改成小一点的值,再来试试 P[x] 是否等于

    P[now].  now该缩小到多少呢?显然,我们不想让now缩小太多。因此我们决定,在保持“P[0]~P[x-1]的now-前缀仍然等于now-后缀”的前提下,让这个新的now尽可能大一点。 P[0]~P[x-1] 的公共前后缀,前缀一定落在串A里面、后缀一定落

    在串B里面。换句话讲:接下来now应该改成:使得 A的k-前缀等于B的k-后缀 的最大的k.  您应该已经注意到了一个非常强的性质——串A和串B是相同的!B的后缀等于A的后缀!因此,使得A的k-前缀等于B的k-后缀的最大的k,其实就是串A的最长

    公共前后缀的长度 —— next[now-1]!

    来看上面的例子。当P[now]与P[x]不相等的时候,我们需要缩小now——把now变成next[now-1],直到P[now]=P[x]为止。P[now]=P[x]时,就可以直接向右扩展了。

  • 相关阅读:
    [转]老男孩读pcie
    nand
    面试题目汇总
    redis的tcp-backlog配置
    Redis 3.0.4 sentinels
    Redis 3.0.4 客户端
    Redis 3.0.4 事件驱动
    Redis 3.0.4 AOF持久化
    Redis 3.0.4 数据库
    Redis 3.0.4 RDB持久化
  • 原文地址:https://www.cnblogs.com/bangdexuanyuan/p/13386445.html
Copyright © 2011-2022 走看看