next 表是 KMP 算法的核心内容,下面介绍一种计算 next 表的方法:利用递推式计算
如图 6.3.1 所示,在某一趟匹配中,当对比到最后一个字符的时候,发现匹配失败(s[i] ≠ t[j])。根据 BF 算法,则令 i = i - j + 1; j=0 ,两个字符串的指针同步回退,然后从该位置开始继续比对。

在下一趟匹配过程中,对于模式串 t 而言,还要从头开始,尽管有一些字符在前一趟匹配中已经比对过且成功。如此就造成了时间上的浪费。
以图 6.3.1 为例,s[i-j, i) 完全是由 0 组成的,那么,在 i, j 回退之后的下一趟比对中,前
j
−
1
j-1
j−1 次比对必然会成功。因此,可以令 i 不回退,保持不变,令 j = j - 1 。这样,就减少了比对次数。
上述“令 i 保持不变、j = j - 1”的含义,可以理解为:令 t 相对于 s 向右移动一个单元,然后从前一个匹配失败位置继续比对,如图 6.3.2 所示。

将上述案例推广到稍微一般的情况,如图 6.3.3 所示,在某一轮匹配中,s[i]='E' ≠ 'O'=t[4] ,在此为止匹配失败后,由于 s[i-4, i) = t[0, 4) = 'REGR' ,即 s[i] 左侧的若干字符已经匹配,所以,接下来只需将 t[0] 与 s[i-1] 对齐即可,或者说将 t 向右移动 4-1=3 个字符,等效于 i 保持不变,同时令 j = 1 ,然后继续匹配。

将上面的讨论,再向更一般化的情况推广,如图 6.3.4 所示,假设前一趟比对终止于 s[i] ≠ t[j] ,按照上述操作,指针 i 不必回退,而是将 s[i] 与 t[k] 对齐,并进行下一趟比对。
那么,问题是 k 应该如何确定?

结合图 6.3.4 以及前面两个案例,经过前一趟比对,已经确定匹配的范围是:t[0, j) = s[i-j, i) 。
于是,若模式串 t 经适当向右移动之后,能够与 s 的某一子串完全匹配(包含 s[i] 在内),则其中的必要条件是:t[0, k) = s[i-k, i) = t[j-k, j) 。
也就是,在 t[0, j) 中长度为 k 的真前缀 t[0, k) ,应与长度为 k 的真后缀 t[j-k, j] 完全匹配。
空串是任何字符串的子串,也是任何字符串的前缀和后缀; 任何字符串都是自己的子串,也是自己的前缀和后缀。此类子串、前缀和后缀分别称作平凡子串(trivial substring)、平凡前缀(trivial prefix)和平凡后缀(trivial suffix)。
字符串本身之外的所有非空子串、前缀和后缀,分别称作真子串(proper substring)、 真前缀(proper prefix)和真后缀(proper suffix)。
所以,k 必然来自集合:
N
(
t
,
j
)
=
{
0
≤
k
<
j
∣
t
[
0
,
k
)
=
t
[
j
−
k
,
j
)
}
N(t, j) = \{0\le k\lt j~|~t[0, k) = t[j-k,j)\}
N(t,j)={0≤k<j ∣ t[0,k)=t[j−k,j)}
一般地,该集合可能包含多个符合条件的 k 。但需要特别注意的是,其中具体由哪些 k 值构成,仅取决于模式串 t 以及前一趟比对的首个匹配失败位置 t[j] ,而与主串 s 无关。
从图 6.3.4 还可以看出,若下一轮比对从 s[i] 与 t[k] 的比对开始,这等效于将 t 向右移动 j - k 个单元,位移量与 k 负相关。因此,为保证 t 与 s 的对齐位置不倒退(指针 i 不回退),同时又不至于遗漏任何可能得匹配,应在集合
N
(
t
,
j
)
N(t, j)
N(t,j) 中挑选最大的 k 。也就是说,当有多个 k 值时,即多个向右移动 t 的方案时,应该保守地选择其中移动距离最短者,即取 k 最大。于是,令:
next[j]
=
max
(
N
(
t
,
j
)
)
\text{next[j]} = \max(N(t,j))
next[j]=max(N(t,j))
则一旦发现 t[j] 与 s[i] 匹配失败,即可转而将 t[ next[j] ] 与 s[i] 对齐,并从这一位置开始继续下一趟比对。
既然集合
N
(
t
,
j
)
N(t, j)
N(t,j) 仅取决于模式串 t 以及匹配失败位置 j,而与主串 s 无关,作为其中的最大元素, next[j] 也必然具有这一性质。于是,对于任一模式串 t ,不妨通过预处理提前计算出所有位置 j 所对应的next[j] 值,并整理为表格(即下面介绍的“next 表”),以便此后反复查询——亦即,将“记忆力”转化为“预知力”。
根据 N ( t , j ) N(t,j) N(t,j) 集合可知,只要 j > 0 j\gt 0 j>0 ,必然有 0 ∈ N ( t , j ) 0\in N(t,j) 0∈N(t,j) 。此时集合 N ( t , j ) N(t,j) N(t,j) 非空,从而可以保证“在其中能够取到最大值”,即 next[j] = max ( N ( t , j ) ) \text{next[j]} = \max(N(t,j)) next[j]=max(N(t,j)) 。
- 令
next[0] = -1
当
j
=
0
j=0
j=0 时,即 t 中的第一个字符与 s 中的字符不匹配,集合
N
(
t
,
j
)
N(t,j)
N(t,j) 为空集,此时,next[j=0] 的值应该是多少?
当某一趟匹配中,如果模式串 t 的第一个字符即匹配失败,根据匹配过程可知,应该将模式串 t 向右移动一个字符,然后启动下一趟匹配。为此,如果假想模式串 t 的首字符 t[0] 的左侧再有一个字符,可以记作 t[-1] ,并且该字符与任何字符都是匹配的。那么,在这趟本来是 t 的首字符匹配失败的操作中,就可以认为 t[-1] 与主串中对应字符匹配成功,于是“将模式串 t 向右移动一个字符”就可以认为是将 t[-1] 与 s[i] 对齐。
经过上述分析,当
j
=
0
j=0
j=0 时,可认为 next[0] = -1 。
- 计算
next[j + 1]
前面已经讲过,next[j] 与主串 s 无关,取决于模式串 t 以及匹配失败的位置 j 。于是,“对于任一模式串 t ,不妨通过预处理提前计算出所有位置 j 所对应的next[j] 值,并整理为表格(即下面介绍的“next 表”),以便此后反复查询”。
在确定了 next[0] = -1 之后,后续的 next[j] 可以使用递推方式计算。
假设已知 next[0] 到 next[j] 的值,由此递推出 next[j+1] 的值。
若 next[j] = k ,则意味着在 t[0, j) 中,自匹配的真前缀和真后缀的最大长度为 k ,故必然有:
next[j+1] ≤ next[j] + 1
而且,当且仅当 t[j] = t[k] 时取等号,即 next[j+1] = next[j] + 1 = k + 1 ,如图 6.3.5 所示。

当 t[j] ≠ t[k] 时,则可以不断向前查找,next[next[j]] + 1, next[next[next[j]]] + 1, ... ,直到 t[-1] 这个与任何字符都匹配的字符,在这个过程中,必然会有能够满足 next[j+1] = next[... next[j] ...] + 1 成立的 t[k] (如图 6.3.6 所示)。即:反复用 next[k] 替换 k (令 k = next[k]),一旦发现 t[j] 与 t[k] 匹配(含与 t[k = -1] 的通配),即可令 next[j + 1] = next[k] + 1 .

既然总有 next[k] < k ,故在此过程中 k 必然严格递减;同时,即便 k 降低至 0,亦必然会终止于通配的next[0] = -1,而不致下溢。如此,该算法的正确性完全可以保证。
由此,就可以创建 next 表。
例 6.3.1 已知串 t = '000010' ,计算 next 表(或称:next 数组值)。
【解】
-
设
next[0] = -1。 -
j = 1时,k = next[j-1] = next[0] = -1(此时,前面的 next 数组值已经得到,所以必然有next[j-1] = k,由此可以计算得到k)。比较
t[j-1]和t[k](因为next[j] ≤ next[j-1] + 1,当且仅当t[j-1] = t[k]时取等号):t[j-1] = t[0] = 0,t[k] = t[-1] = *,相等(通配),所以:next[j] = k+1(因为next[j-1] = k,),next[1] = -1+1 = 0。 -
j = 2时,k = next[j-1] = next[1] = 0。因为
t[j-1=1] = 0,t[k=0] = 0,相等,所以next[j=2] = k + 1 = 0 + 1 = 1。 -
j = 3时,k = next[2] = 1。因为
t[j-1=2] = 0,t[k=1] = 0,相等,所以next[j=3] = k + 1 = 1 + 1 = 2。 -
j = 4时,k = next[j-1=3] = 2。因为
t[j-1=3] =0,t[k=2] = 0,相等,所以next[j=4] = k + 1 = 2 + 1 = 3。 -
j = 5时,k = next[j-1=4] = 3。因为
t[j-1=4]=1,t[k=3] = 0,不相等。 则
k = next[k=3] = 2:因为t[j-1=4]=1,t[k=2] = 0,不相等。 则
k = next[k=2] = 1:因为t[j-1=4]=1,t[k=1] = 0,不相等。 则
k = next[k=1] = 0:因为t[j-1=4]=1,t[k=0] = 0,不相等。 则
k = next[k=0] = -1:因为t[j-1=4]=1,t[k=-1] = *,相等(通配)。所以next[j=5] = k + 1 = -1 + 1 = 0。
| Index | -1 | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|---|
t[ ] | * | 0 | 0 | 0 | 0 | 1 | 0 |
next[] | N/A | -1 | 0 | 1 | 2 | 3 | 0 |













![[unity 点击事件] 区域响应点击事件,排除子节点区域,Raycast Target 应用](https://i-blog.csdnimg.cn/direct/53f40f6b699e4dd1afd76a5d403af840.png)





