求前缀函数的线性算法(KMP)
我们定义的所有字符串都是以下标 \(0\) 开头的。
首先定义字符串 \(p\),长度为 \(k\),其第 \(i+1\) 位字符为 \(p_i\),以 \(p_i\) 为结尾字符的长度为 \(i+1\) 的前缀为 \(t_i\).
定义 \(p\) 的前缀函数 \(\pi_i\),\(\pi_i\) 为 \(t_i\) 的最长的、对应一个与之相同的 \(t_i\) 的真后缀的真前缀的长度。
我们可以朴素地计算 \(pi\):
for(int i=1;i<k;i++) {
for(;pi[i]<i+1;pi[i]++) { // 枚举长度
if(!str_same(p,p+j+1,p+i-j+1,p+i)) break;
}
}
这样的时间复杂度是 \(\Theta(n_2)\) 到 \(\Theta(n_3)\) 的,取决于 str_same 是哈希实现的还是朴素实现的。
不难发现,枚举长度的时候,我们有很多冗余的字符串比对。如果我们已经知道了 \(\pi_{i-1}\),那么 \(t_{\pi_{i-1}-1} = p[i-\pi_{i-1}+1...i-1]\) 是显然的。
并且基于定义,必然有 \(\pi_i \le \pi_{i-1}+1\). 这在 \(p_i = p_{\pi_{i-1}}\) 时候取到。
我们只需要知道 \(p_i \ne p_{\pi_{i-1}}\) 时,\(\pi_i\) 的取值就可以去掉字符串比对了。

观察,我们实际上需要找的就是这个更短的绿框,满足灰色的圆角方形等于黑色的圆角方形。不难发现它的长度就等于 \(\pi_{\pi{i-1}}\),由定义。(把后面的红框和绿框平移到左边红框,发现两个绿框代表了相等的、红色框的真前缀和真后缀)
所以我们能写出以下线性(并不显然)算法:
for(int i=1;i<k;i++) {
pi[i] = pi[i-1];
while(1) {
if(p[pi[i]] == p[i]) break;
if(pi[i] == 0) break;
pi[i] = pi[pi[i]-1];
}
}

浙公网安备 33010602011771号