邓俊辉老师的课上给出了一种普遍的kmp算法优化,但是没有给出这种优化的正确性证明。或许这种正确性是显而易见的,但这里还是研究了一下这个算法的正确性问题。
int* buildNext(char* P) {
int n = strlen(P);
int* next = new int[n];
int i = 0, j = next[0] = -1;
while (i < n - 1) {
if (j < 0 || P[i] == P[j]) {
++i;
++j;
//原算法
next[i] = j;
//优化算法
newNext[i] = P[i] != P[j] ? j : newNext[j];
}
else
//原算法
j = next[j];
//优化算法
j = newNext[j];
}
return next;
}
显然,算法的优化核心即在于对P[i]
和P[j]
相等时的处理,亦即,若目标串T[k]
与P[i]
不可匹配,则T[k]
必然与P[j]
不可匹配,回溯至j
显然是一种远远不够的回溯,按照原算法的运行逻辑,则会递归j = next[j]
直至T[k] = P[i] != P[j]
。很自然的,我们可以通过递推改进这个递归公式,这个的正确性是显然的,通过保证前面满足条件来保证后面均满足条件。
优化算法的实际变动不仅于此,由于改变的next
数组的含义,那么显然,我们需要保证j = newNext[j]
仍能发挥原先的作用,才可保证j
的取值满足newNext
数组的正确性。显然,在这个算法中,newNext
数组表示的是在P[0, j)中,P[0, t) = P[j-t, j) && P[t] != P[j] 情况下t的最大值
。因此,若P[i] != P[j]
,则可令k = j
,递归k = next[k]
直至P[j] != P[k]
,此时P[i]
与P[j]
方有相同的可能。此回溯与newNext
数组的含义不谋而合,因此我们可以使用j = newNext[j]
来代替原算法的j = next[j]
,算法的功能仍得以保存。
因此,整个优化算法过程中,j
的变化仍与原算法相同,仅有些微小的变化,而这些微小的变化仅仅是“跳步”迭代而已,这保证了next
数组与newNext
数组在P[i] != P[j]
情况下取值相同,算法的正确性得以保证。
这个算法我脑子里有个坎一直过不去,老觉得这有问题那有问题,想通了就非常简单,没想到写出来能这么恶心,写了好久仍然只写出了个大概意思,各位看官将就着看吧,有哪里没有写清楚的可以评论探讨。