其中, 即是 的值。 是模式串的第 个字符。
上面我们仅给出了 Next 数组的基本框架,当然还有边界情况的取值。而 值究竟怎么算仍然还是问题,接下来我们将进一步解决这个问题。这也是 KMP 算法的精髓。
假设我们已经知道了前 个字符对应的 值,且已知 ,此时我们知道,前 个字符构成的字符串中,前缀取 个,后缀取 个,二者是对应相等的。
用 T[1..j]
表示的话,即有 T[1..k-1] == T[j-k+1..j-1]
.
现在欲计算 的值。有如下几种情况:
T[1..k] == T[j-k+1..j]
,所以得到.T[1..k'] = T[j-k'+1..j]
。对于第二种情况,我们需要展开论述。
因为我们要找的新的值 能使得 T[1..k'] = T[j-k'+1..j]
,所以找 的过程,其实就是一个新的小型的模式匹配问题。即把在原本的前缀 T[1..k]
作为模式串,把包括第 个字符的整个后缀 T[j-k+1..j]
视为主串,企图在新后缀中找到与模式串相重叠的部分,这个长度就是。
显然,因为,所以第一步匹配时一定会失败,并且此时显示前 个字符是匹配成功的,这时,我们需要将作为模式串的 T[1..k]
整体回退到下标为 Next[k]
的位置。
这也就是说,在 T[1..Next[k]-1]
是匹配成功的情况下,对比 T[Next[k]]
的匹配结果,即比较。如果匹配失败,就继续回退到 T[Next[Next[k]]]
的位置。如此往复,直到匹配成功得到 或是匹配失败。失败时我们在上面给了规定,取。
下面给出一个示例,以便更加清晰的理解。
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
---|---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | a | b | c | a | b | a |
0 | 1 | 1 | 2 | 2 | 3 | ? | ? | ? |
假设已知前 的 值,现求 的 值。
首先,比较. 也就是 T[3] = "a"
和 T[6] = "c"
的值,显然不等。
又因为 ,这说明,即在 之前长为 5 的字符串中,有大小最大是 2 的公共前后缀,即 T[1..2] = "ab" = T[4..5]
.
前面我们比较的 的结果就是在告诉我们,这两个公共前后缀都往右延伸一位后就发生了不匹配,于是我们需要把 T[1..3] = "aba"
和 T[4..6] = "abc"
分别作为模式串和主串进行模式匹配,以找到新的最长公共前后缀。
因为比较发现前两位是匹配的,而第三位不匹配,所以我们查表,值为1表示此时我们要做的操作是 T[4..6]
不动,将 T[1..3]
的 T[Next[3]] == T[1]
与 T[6]
对齐(也就是说主串不动,从当前位置把模式串从头开始比较)。此时 T[1]
与 T[6]
还是不等,所以查表 ,值为0表示不匹配。所以匹配不成功,也就是此时没有哪怕一个公共前后缀,所以根据我们前面讨论的边界条件情况,我们置.
下面计算.
由于 ,也就是 T[1] = "a" = T[7]
,匹配成功,所以 。
再计算.
由于 ,也就是 T[2] = "b" = T[8]
,匹配成功,其实也就是说,在前 位有着长度是 1 的公共前后缀的基础上,往后延伸一位仍然匹配,所以最长公共前后缀的数值增加到2,更进一步,也就是有 。
将我们之前的分析利用代码实现如下:
1 | /* 传入模式串,根据模式串返回对应next数组 */ |
前面定义的 Next 数组在某些情况下,其实是有缺陷的,还可以进一步优化。
比如 S = "aaabaaaab", T = "aaaab"
经计算,可得模式串 的 数组如下:
模式串 | a | a | a | a | b |
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | |
0 | 1 | 2 | 3 | 4 |
则,当主串的第 4 位与模式串的第 4 位比较时,发现 S[4] = "b"
,T[4] = "a"
二者不等。此时,根据原算法,我们需要比较 S[4], T[Next[4]] == T[3]
,而 T[3]
显然也是与 T[4]
不等的,不仅是因为取出来比较时发现不等,而且因为 T[3] == T[4]
。
也就是说,当我们回退 的时候,如果发现 ,那么这个回退其实是没有意义的,我们还得继续回退,白白浪费了一次无意义的比较。
经过上述分析,我们总结得出,如果在 get_next()
阶段,我们就发现回退前后的字符的值相同,那么不如把 值都调整为第一个不相同的值。
比如上面这个例子中,我们依次遍历模式串 时,T[1] = "a"
,根据边界条件1,我们置 Next[1] = 0
。而计算 Next[2]
时,发现正好T[2] == T[1]
,那么我们也置 Next[2] = 0
.
于是,我们得到 Next 数组的改进方法:
1 | /* 改进的next数组获取 */ |