什么是 后缀(Suffix)
S是一个长度为 N 的字符串,那么定义S的第 i 个后缀就是S的子串 S[i...n-1]
什么是 后缀数组(Suffix Array)
后缀数组作为一种数据结构,被广泛应用于数据压缩、生物信息学等领域。通俗地说后缀数组被应用于任何处理字符串与字符串匹配的场合
。
后缀数组是各个排序后的后缀的起始位置组成的数组。
示例
当 S = abaab 时。
所有的后缀如下:
|
|
依据字符串的比较对后缀进行排序后得到:
|
|
因此字符串S的后缀数组为: (2, 3, 0, 4, 1)
后缀数组的构造
O(N2logN) 构造法
这是最朴素的构造方法效率,即获取所有后缀并使用O(NlogN)的排序算法如快排或者合并排序对这些后缀进行排序。而构造法的时间复杂度是O(N2logN)而不是O(NlogN)的原因是排序过程中两个后缀的比较复杂度为O(N)。下面我们将把比较的复杂度从O(N)降为O(logN)。
O(N log2 N) 构造法
比较的复杂度能够从O(N)降为O(logN)依赖于后缀的特性:这些后缀并不是随机的,而是同属于一个字符串的后缀,也就是说这些后缀有相同的公共部分可以供我们利用。
下面用示例说明。我们以所有后缀的首字母为基准排序,并对这些首字母赋予一个 rank 值代表他们之间的大小关系。
|
|
如上,以 | 左侧字符串为比较基准,字符串前的数字表示 | 左侧字符串对应的 rank。由此,rank(a) = 0, rank(b) = 1。
我们取两个字符进行比较时,比较分两步。首先比较第一个字符,如果相等则继续比较第二个字符。
1、对于 abaab 和 baab,因为二者首字母 rank 值已知且不同,因此 abaab 排序在 baab 之前。
2、对于 abaab 和 aab,因为而这首字母 rank 值已知且相同,因此继续看第二个字符,两者的第二个字符不同且 rank 值已知,所以 aab 排在 abaab 之前。
通过上面两个步骤的解析可以发现,在比较所有后缀的第一个字符之后,我们就已经获得了整个字符串 S 的所有字符的 rank 值。(因为所有后缀的首字符组在一起恰好为字符串S,联想后缀的定义即S从i(0 <= i < len(s))到S的最后一个字符所组成的字符串集合)
对于没有第二个字符的字符串我们认为它的第二部分 rank 值为最小值,这里设其为-1。例如 b 只有一个字符,因此它的前两个字符 rank 对就为(1, -1)
现在我们尝试以两个字符作为比较基准,有:
|
|
综上所述,对于字符串下次比较,我们选取4个字符,而4个字符的比较又可分为两部分:前两个字符和后两个字符。这又回到了上一步的过程。因此比较两个字符串时,我们只需比较前1、2、4、8、…、log(len(s))个字符。
下面是代码示例:
|
|
后缀数组的应用
1、结合LCP(Longest Common Prefix)
2、模式匹配
3、 查找最长重复子串
4、 查找最长公共子串
5、 查找最长回文子串