next数组算法
- 1. 若是对于值k,已有p0 p1, ..., pk-1 = pj-k pj-k+1, ..., pj-1,至关于next[j] = k。
- 此意味着什么呢?究其本质,next[j] = k 表明p[j] 以前的模式串子串中,有长度为k 的相同前缀和后缀。有了这个next 数组,在KMP匹配中,当模式串中j 处的字符失配时,下一步用next[j]处的字符继续跟文本串匹配,至关于模式串向右移动j - next[j] 位。
举个例子,以下图,根据模式串“ABCDABD”的next 数组可知失配位置的字符D对应的next 值为2,表明字符D前有长度为2的相同前缀和后缀(这个相同的前缀后缀即为“AB”),失配后,模式串须要向右移动j - next [j] = 6 - 2 =4位。数组

向右移动4位后,模式串中的字符C继续跟文本串匹配。spa
- 2. 下面的问题是:已知next [0, ..., j],如何求出next [j + 1]呢?
对于P的前j+1个序列字符:.net
- 若p[k] == p[j],则next[j + 1 ] = next [j] + 1 = k + 1;
- 若p[k ] ≠ p[j],若是此时p[ next[k] ] == p[j ],则next[ j + 1 ] = next[k] + 1,不然继续递归前缀索引k = next[k],然后重复此过程。 至关于在字符p[j+1]以前不存在长度为k+1的前缀"p0 p1, …, pk-1 pk"跟后缀“pj-k pj-k+1, …, pj-1 pj"相等,那么是否可能存在另外一个值t+1 < k+1,使得长度更小的前缀 “p0 p1, …, pt-1 pt” 等于长度更小的后缀 “pj-t pj-t+1, …, pj-1 pj” 呢?若是存在,那么这个t+1 即是next[ j+1]的值,此至关于利用已经求得的next 数组(next [0, ..., k, ..., j])进行P串前缀跟P串后缀的匹配。
通常的文章或教材可能就此一笔带过,但大部分的初学者可能仍是不能很好的理解上述求解next 数组的原理,故接下来,我再来着重说明下。
以下图所示,假定给定模式串ABCDABCE,且已知next [j] = k(至关于“p0 pk-1” = “pj-k pj-1” = AB,能够看出k为2),现要求next [j + 1]等于多少?由于pk = pj = C,因此next[j + 1] = next[j] + 1 = k + 1(能够看出next[j + 1] = 3)。表明字符E前的模式串中,有长度k+1 的相同前缀后缀。
但
若是pk != pj 呢?说明“p0 pk-1 pk” ≠ “pj-k pj-1 pj”。换言之,当pk != pj后,字符E前有多大长度的相同前缀后缀呢?很明显,由于C不一样于D,因此ABC 跟 ABD不相同,即字符E前的模式串没有长度为k+1的相同前缀后缀,也就不能再简单的令:next[j + 1] = next[j] + 1 。因此,我们只能去寻找长度更短一点的相同前缀后缀。
结合上图来说,若能
在前缀
“ p0 pk-1 pk ” 中不断的递归前缀索引k = next [k],找到一个字符pk’ 也为D,表明pk’ = pj,且知足p0 pk'-1 pk' = pj-k' pj-1 pj,则最大相同的前缀后缀长度为k' + 1,从而next [j + 1] = k’ + 1 = next [k' ] + 1。不然前缀中没有D,则表明没有相同的前缀后缀,next [j + 1] = 0。
那为什么递归前缀索引k = next[k],就能找到长度更短的相同前缀后缀呢?这又归根到next数组的含义。
咱们拿前缀 p0 pk-1 pk 去跟后缀pj-k pj-1 pj匹配,若是pk 跟pj 失配,下一步就是用p[next[k]] 去跟pj 继续匹配,若是p[ next[k] ]跟pj仍是不匹配,则须要寻找长度更短的相同前缀后缀,即下一步用p[ next[ next[k] ] ]去跟pj匹配。此过程至关于模式串的自我匹配,因此不断的递归k = next[k],直到要么找到长度更短的相同前缀后缀,要么没有长度更短的相同前缀后缀。以下图所示:
因此,因最终在前缀ABC中没有找到D,故E的next 值为0:
模式串的后缀:ABDE
模式串的前缀:ABC
前缀右移两位: ABC
读到此,有的读者可能又有疑问了,那可否举一个能在前缀中找到字符D的例子呢?OK,我们便来看一个能在前缀中找到字符D的例子,以下图所示:
给定模式串DABCDABDE,咱们很顺利的求得字符D以前的“DABCDAB”的各个子串的最长相同前缀后缀的长度分别为0 0 0 0 1 2 3,但当遍历到字符D,要求包括D在内的“DABCDABD”最长相同前缀后缀时,咱们发现pj处的字符D跟pk处的字符C不同,换言之,前缀DABC的最后一个字符C 跟后缀DABD的最后一个字符D不相同,因此不存在长度为4的相同前缀后缀。
怎么办呢?既然没有长度为4的相同前缀后缀,我们能够寻找长度短点的相同前缀后缀,最终,因在p0处发现也有个字符D,p0 = pj,因此p[j]对应的长度值为1,至关于E对应的next 值为1(即字符E以前的字符串“DABCDABD”中有长度为1的相同前缀和后缀)。
综上,能够经过递推求得next 数组,代码以下所示:
- void GetNext(char* p,int next[])
- {
- int pLen = strlen(p);
- next[0] = -1;
- int k = -1;
- int j = 0;
- while (j < pLen - 1)
- {
-
- if (k == -1 || p[j] == p[k])
- {
- ++k;
- ++j;
- next[j] = k;
- }
- else
- {
- k = next[k];
- }
- }
- }
用代码从新计算下“ABCDABD”的next 数组,以验证以前经过“最长相同前缀后缀长度值右移一位,而后初值赋为-1”获得的next 数组是否正确,计算结果以下表格所示:code

从上述表格能够看出,不管是以前经过“最长相同前缀后缀长度值右移一位,而后初值赋为-1”获得的next 数组,仍是以后经过代码递推计算求得的next 数组,结果是彻底一致的。blog
3.3.5 基于《next 数组》匹配
下面,咱们来基于next 数组进行匹配。递归

仍是给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,如今要拿模式串去跟文本串匹配,以下图所示:索引

在正式匹配以前,让咱们来再次回顾下上文2.1节所述的KMP算法的匹配流程:ip
- “假设如今文本串S匹配到 i 位置,模式串P匹配到 j 位置
- 若是j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
- 若是j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
- 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j - next[j],且此值大于等于1。”
- 1. 最开始匹配时
- P[0]跟S[0]匹配失败
- 因此执行“若是j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]”,因此j = -1,故转而执行“若是j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++”,获得i = 1,j = 0,即P[0]继续跟S[1]匹配。
- P[0]跟S[1]又失配,j再次等于-1,i、j继续自增,从而P[0]跟S[2]匹配。
- P[0]跟S[2]失配后,P[0]又跟S[3]匹配。
- P[0]跟S[3]再失配,直到P[0]跟S[4]匹配成功,开始执行此条指令的后半段:“若是j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++”。
- 2. P[1]跟S[5]匹配成功,P[2]跟S[6]也匹配成功, ...,直到当匹配到P[6]处的字符D时失配(即S[10] != P[6]),因为P[6]处的D对应的next 值为2,因此下一步用P[2]处的字符C继续跟S[10]匹配,至关于向右移动:j - next[j] = 6 - 2 =4 位。

- 3. 向右移动4位后,P[2]处的C再次失配,因为C对应的next值为0,因此下一步用P[0]处的字符继续跟S[10]匹配,至关于向右移动:j - next[j] = 2 - 0 = 2 位。

- 4. 移动两位以后,A 跟空格不匹配,模式串后移1 位。

- 5. P[6]处的D再次失配,由于P[6]对应的next值为2,故下一步用P[2]继续跟文本串匹配,至关于模式串向右移动 j - next[j] = 6 - 2 = 4 位。

匹配过程如出一辙。也从侧面佐证了,next 数组确实是只要将各个最大前缀后缀的公共元素的长度值右移一位,且把初值赋为-1 便可。字符串
3.3.6 基于《最大长度表》与基于《next 数组》等价
咱们已经知道,利用next 数组进行匹配失配时,模式串向右移动 j - next [ j ] 位,等价于已匹配字符数 - 失配字符的上一位字符所对应的最大长度值。缘由是:
- j 从0开始计数,那么当数到失配字符时,j 的数值就是已匹配的字符数;
- 因为next 数组是由最大长度值表总体向右移动一位(且初值赋为-1)获得的,那么失配字符的上一位字符所对应的最大长度值,即为当前失配字符的next 值。
但为什么本文不直接利用next 数组进行匹配呢?由于next 数组很差求,而一个字符串的前缀后缀的公共元素的最大长度值很容易求。例如若给定模式串“ababa”,要你快速口算出其next 数组,乍一看,每次求对应字符的next值时,还得把该字符排除以外,而后看该字符以前的字符串中有最大长度为多大的相同前缀后缀,此过程不够直接。而若是让你求其前缀后缀公共元素的最大长度,则很容易直接得出结果:0 0 1 2 3,以下表格所示:

而后这5个数字 所有总体右移一位,且初值赋为-1,即获得其next 数组:-1 0 0 1 2。