本文是对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继承
这里要注意了,想是这样想,编程怎么实现呢?
只要按照下面的规则:
当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,若是多加了一个字符,要对称的话最可能是当前的和第一个对称。好比agcta这个里面t的是0,那么后面的a的对称程度只须要看它是否是等于第一个字符a了。
按照这个推理,咱们就能够总结一个规律,不只前面是0呀,若是前面一个字符的next值是1,那么咱们就把当前字符与子串第二个字符进行比较,由于前面的是1,说明前面的字符已经和第一个相等了,若是这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。好比上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着咱们就把最后一个g与第二个g比较,又相等,天然对称成都就累加了,就是2了。
按照上面的推理,若是一直相等,就一直累加,能够一直推啊,推到这里应该一点难度都没有吧,若是你以为有难度说明我写的太失败了。
固然不可能会那么顺利让咱们一直对称下去,若是遇到下一个不相等了,那么说明不能继承前面的对称性了,这种状况只能说明没有那么多对称了,可是不能说明一点对称性都没有,因此遇到这种状况就要从新来考虑,这个也是难点所在。
这里已经不能继承前面了,可是仍是找对称成都嘛,最愚蠢的作法大不了写一个子函数,查找这个字符串的最大对称程度,怎么写方法不少吧,好比查找出全部的当前字符串,而后向前走,看是否一直相等,最后走到子串开头,固然这个是最蠢的,咱们通常看到的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的对称性就要从新来求。
这里首要要申明几个事实
t 若是要存在对称性,那么对称程度确定比前面这个c 的对称程度小,因此要找个更小的对称,这个不用解释了吧,若是大那么t就继承前面的对称性了。
要找更小的对称,必然在对称内部还存在子对称,并且这个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]; } }