浅谈后缀数组

发布时间 2023-09-01 22:01:00作者: Diavolo-Kuang

编写中,待完善。。。

前置知识 : 后缀(???),基数排序(说通俗一点就是桶子排序),基础倍增。

后缀数组是一种处理字符串问题的利器,可以起到代替后缀树的作用,在码量上具有绝对的优势。正常情况下,大家都会使用后缀数组而非后缀树。虽然后缀数组十分的好写,但是过程难以令人理解。今天我会使用尽量通俗的语言帮助大家理解什么是后缀数组,以及相关的拓展。

在开始之前

我们需要对于一些变量进行定义(别的数字在后边讲到):

\(sa_i\) 表示排名为 \(i\) 的后缀下标是多少。

\(rk_i\) 表示下标 \(i\) 的后缀的排名是多少。

那么这两个数组是具有性质: \(sa[rk_i]=rk[sa_i]=i\) 。这一点看起来比较的绕,但是还是稍加思考可以理解的。也就是说,我们知道了 \(sa\)\(rk\) 中的一者,就可以在线性的时间内推出另一个。

后缀排序

现在进行了定义,该如何求出这个后缀数组呢?我们讲如下两种方法(最最最最牛逼但是常数和码量极大的 \(\text{DC3}\) 我们暂不讨论):

字符串哈希算法

这个算法的时间复杂度是 \(O(n \log^2 n)\) 。因为相比其他解法较劣势的时间复杂度,并不是很常使用。

相比大家知道怎么编写 \(std::sort\) 的自定义比较函数吧。如果这个比较的时间是 \(O(T)\) 的,那么我们的排序时间就可以做到 \(O(Tn \log n)\) 。那么现在我们的目标就是在尽量块的时间内比较两个后缀的字典序。

最简单的做法就是暴力的枚举,但是注意到 \(LCP\) (最长公共前缀) 是可以二分的,所以我们可以去二分第一个不一样的前缀,比较最后一个字符的大小就可以了。问题就又变换为了如何去比较两个字符串相同,这个显然使用字符串哈希可以在 \(O(1)\) 的时间内解决。那么我们的比较时间就从 \(O(n)\) 优化到了 \(O(n\log n)\)

最终的时间复杂度就是 \(O(n \log^2 n)\)

代码