KMP算法的next[]数组通俗解释

前言

         本文是对KMP核心部分NEXT数组的构建方法说明,KMP总体分析的文章能够参考下面的连接。算法

http://my.oschina.net/u/572632/blog/277548编程

概述

     咱们在一个母字符串中查找一个子字符串有不少方法。KMP是一种最多见的改进算法,它能够在匹配过程当中失配的状况下,有效地多日后面跳几个字符,加快匹配速度。数组

     固然咱们能够看到这个算法针对的是子串有对称属性,若是有对称属性,那么就须要向前查找是否有能够再次匹配的内容。在KMP算法中有个数组,叫作前缀数组,也有的叫next数组,每个子串有一个固定的next数组,它记录着字符串匹配过程当中失配状况下能够向前多跳几个字符,固然它描述的也是子串的对称程度,程度越高,值越大,固然以前可能出现再匹配的机会就更大。这个next数组的求法是KMP算法的关键,但不是很好理解,我在这里用通俗的话解释一下,看到别的地方处处是数学公式推导,看得都蛋疼,这个篇文章仅贡献给不喜欢看数学公式又想理解KMP算法的同窗。函数

分析

     用一个例子来解释,下面是一个子串的next数组的值,能够看到这个子串的对称程度很高,因此next值都比较大。优化


一 逐个查找对称串

这个很简单,咱们只要循环遍历这个子串,分别看前1个字符,前2个字符,3个... i个 最后到15个。spa

第1个a无对称,因此对称程度0.net

前两个ag无对称,因此也是0code

依次类推前面0-4都同样是0blog

前5个agcta,能够看到这个串有一个a相等,因此对称程度为1前6个agctag,看获得ag和ag对成,对称程度为2继承

这里要注意了,想是这样想,编程怎么实现呢?

只要按照下面的规则:

  1. 当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,若是多加了一个字符,要对称的话最可能是当前的和第一个对称。好比agcta这个里面t的是0,那么后面的a的对称程度只须要看它是否是等于第一个字符a了。

  2. 按照这个推理,咱们就能够总结一个规律,不只前面是0呀,若是前面一个字符的next值是1,那么咱们就把当前字符与子串第二个字符进行比较,由于前面的是1,说明前面的字符已经和第一个相等了,若是这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。好比上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着咱们就把最后一个g与第二个g比较,又相等,天然对称成都就累加了,就是2了。

  3. 按照上面的推理,若是一直相等,就一直累加,能够一直推啊,推到这里应该一点难度都没有吧,若是你以为有难度说明我写的太失败了。

固然不可能会那么顺利让咱们一直对称下去,若是遇到下一个不相等了,那么说明不能继承前面的对称性了,这种状况只能说明没有那么多对称了,可是不能说明一点对称性都没有,因此遇到这种状况就要从新来考虑,这个也是难点所在。

二 回头来找对称性

这里已经不能继承前面了,可是仍是找对称成都嘛,最愚蠢的作法大不了写一个子函数,查找这个字符串的最大对称程度,怎么写方法不少吧,好比查找出全部的当前字符串,而后向前走,看是否一直相等,最后走到子串开头,固然这个是最蠢的,咱们通常看到的KMP都是优化过的,由于这个串是有规律的。

在这里依然用上面表中一段来举个例子:   

位置i=0到14以下,我加的括号只是用来讲明问题:

(a g c t a g c )( a g c t a g c) t

咱们能够看到这段,最后这个t以前的对称程度分别是:1,2,3,4,5,6,7,倒数第二个c往前看有7个字符对称,因此对称为7。可是到最后这个t就没有继承前面的对称程度next值,因此这个t的对称性就要从新来求。

这里首要要申明几个事实

  1. t 若是要存在对称性,那么对称程度确定比前面这个c 的对称程度小,因此要找个更小的对称,这个不用解释了吧,若是大那么t就继承前面的对称性了。

  2. 要找更小的对称,必然在对称内部还存在子对称,并且这个t必须紧接着在子对称以后。

代码

从上面的理论咱们就能获得下面的前缀next数组的求解算法。
void SetPrefix(const char *Pattern, int prefix[])
{
     int len=CharLen(Pattern);//模式字符串长度。
     prefix[0]=0;
     for(int i=1; i<len; i++)
     {
         int k=prefix[i-1];
 //不断递归判断是否存在子对称,k=0说明再也不有子对称,Pattern[i] != Pattern[k]说明虽然对称,可是对称后面的值和当前的字符值不相等,因此继续递推
         while( Pattern[i] != Pattern[k]  &&  k!=0 )               
             k=prefix[k-1];     //继续递归
         if( Pattern[i] == Pattern[k]) 
//找到了这个子对称,或者是直接继承了前面的对称性,这两种都在前面的基础上++
              prefix[i]=k+1;
         else
              prefix[i]=0;       //若是遍历了全部子对称都无效,说明这个新字符不具备对称性,清0
     }
}

优化

KMP还有一种写法:这个写法是通过N我的优化的:
int  j = -1,  i = 0;
next[0] = -1;
while(i < len)
{
          if(j == -1 || ss[i] == ss[j])
         {
                    i++;
                    j++;
                    next[i] = j;
         }
         else
        {
                   j = next[j];
        }
}
相关文章
相关标签/搜索