KMP算法中的next数组学习

这两天再次看了KMP算法,上次看这个算法好像是3,4个月前的事情吧,那时候看了一篇比较详细的博客之后就感觉自己弄懂了,去过了两个模板题之后就飘了,事实证明这是错觉..ORZ。现在再看发现好像找不到了之前那篇比较详细的博客了。现在去找那些博客发现讲的看起来很详细,但是我看着好蛋疼啊,可能是我不习惯于模拟吧,所以我决定把这两天自己弄懂的KMP中的next数组(自己认为弄懂了)来给大家讲解一下,我们可能不会像他们那样模拟的那么仔细,如果有什么错误的地方请大家指出来,我会及时修改的。
照例子来介绍一下KMP:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
本篇文章主要参考了知乎大佬逍遥行的解释,有兴趣的可以去看一下,我会在文章最后发上去我看过参考过的资料。
在说KMP之前我们要先弄懂什么是前缀和后缀:
就拿字符串ABCDEFGH来说,前缀就是前面开始的,比如字符串中的第一位开始的ABC,而第二位开始的BCD不算,第三位开始的CDE也不算。
而后缀是从后面开始的,就是FGH,而EFG不算,必须从后面开始。
还有就是请注意如果字符串是单独的一个A的话,那么A既不属于前缀也不属于后缀。
好了,我来说一下我这两天学到的KMP吧:
KMP匹配中起到最大作用的和精华的就是next数组,而next数组的作用是干嘛的呢?就是找到模式串中的最大前缀后缀。
怎么找呢?通过对自己自身的匹配来寻找最大前缀后缀。
先上代码:

void getNext()
{
    Next[0]=-1;
    int k=-1,plen=strlen(p),j=0;
    while(j<plen)
    {
        if(k==-1||p[k]==p[j])
        {
            j++;
            k++;
            Next[j]=k;
        }
        else
        k=Next[k];
    }
}

我们来说一下next数组的作用:
A:abbaabbaaba
B:abbaaba
A中找B。
当匹配到第七个的时候发现不对了。那么按照以往暴力的做法我们就要从将A进一位,B从0开始重新匹配了,但是我们可以用一些以往的信息来进行优化,怎么优化呢?
那么我们可以看一下next数组,即他们的最大前缀后缀长度。

为什么要执着于最大前缀后缀长度呢?
你可以这样想,当你
abbaabbaaba
abbaaba
第七个不同的时候,按暴力的思想是不是要从头来过呢?但是我们是可以优化的,这时候我们就可以利用最大前缀后缀来优化了,你想我们可以看到已经匹配成功的B的前面6个字符的前面两个ab和后面的ab相同,那么我们就可以把前面的ab挪到后面的ab的位置中去。所以就变成了
abbaabbaaba
abbaaba
你就会发现神奇的地方了。A的5,6位不用在匹配,因为B中后面的ab与前面的ab是一样的,这个过程中相当于挪动了j-Next[k]个位置(这个怎么理解呢?可以这样想,你100变成2,就相当于100-2=98,从100到2挪动了98位。),所以当你挪动的时候就可以节省时间,不用从0开始再来。所以也为什么是强调最大前缀后缀了。

那么为什么可以这样呢?因为是匹配到B的后面的ab的后面一位a出现错误的,那么就是说前面的匹配是没有错的,
那么我们就可以去寻找一下已经匹配对的字符串(注意是已经匹配对的字符串中,不包括匹配出错的a)中是否有最大前缀后缀呢?
刚好后面的ab可以找到前面的ab构成最大前缀后缀。
就相当于模式串abababzabababa
最大前缀后缀:0012340123456?
一直算到 6 都很容易。在往下算之前,先回顾下我们所做的工作:子字符串 abababzababab 前缀后缀最大匹配了 6 个(ababab),那次大匹配了多少呢?
容易看出次大匹配了 4 个(abab),更仔细地观察可以发现,次大匹配的前缀后缀只可能在 ababab 中,直接查上表可以得出该值为 4。
第三大的匹配同理,它既然比 4 要小,那前缀后缀也只能在 abab 中找,查表可得该值为 2。
再往下就没有更小的匹配了。
而我们说的最大匹配其实找的是最大前缀后缀。看到这里是不是突然之间开窍了呢?没错也就是说次大前缀后缀只可能在最大前缀后缀中出现,而第三大前缀后缀就只可能在次小前缀后缀中出现了。这也是为什么当p[k]不等于p[j]的时候k要等于next[k]了,因为如果我们找最大前缀后缀断了的话就要往回找次大的前缀后缀了。因为不用白不用,那么当你k=next[k]之后,p[k]==p[j]那么就可以得到次小前缀后缀长度了,如果一直都找不到了,最后k=-1的时候呢?那就乖乖的再去一个个的匹配吧。
那么为什么一开始的时候Next数组要next[0]=-1呢?这样是为了把整个前缀后缀数组挪动一个位置。 因为我们的字符串存储是从0开始的,那么当我们匹配字符串的时候遇到不匹配的时候就会在不匹配的位置,k=next[k]来挪动模式串,就相当于挪动了j-next[k]位过去,而当你在纸上写的时候就会发现挪动了j-next[k]之后p[k-1,k-2…k-k]跟s[j-1,j-2…j-k]一模一样,为什么会这样呢?这是因为与s[j-1,j-2…j-k]匹配过的对的后缀(不包括那个匹配错误的字符),该后缀对应的的最大前缀也就一定与s[j-1,j-2…j-k]一一对应起来了。
就相当于A:abababe
~~~~~ B:ababe
前面四位是对的 ,但是当e与a匹配时候就发生了错误了,那怎么办呢?当然是看看有没有最大前缀后缀啦?刚好有那就是abab,注意这里就是为什么next数组要挪动一位了,按道理来说ababe的前缀后缀长度应该是00120的,但是当我们匹配到第五位e的时候刚好发现匹配不对,那么我们就要进行k=next[k]=0了,而你如果是00120的话就会发现从0开始了,没有用到前缀后缀的信息。这时候next数组向后挪动一位的重要性出现了,-10012,当你e不对称的时候就会k=next[k]=2去到了ababe中的第二位a中了(字符串从0开始存放)。这时候你们是不是理解了为什么next数组要向后挪动一位了吧?
说了这么多主要是为了让你们理解next数组,next是KMP的精华,当你理解了这个数组之后KMP的匹配就迎刃而解了。

参考的内容有:
https://www.cnblogs.com/zhangtianq/p/5839909.html
知乎的大佬逍遥行的解释https://www.zhihu.com/question/21923021/answer/37475572

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值