后缀数组
后缀数组
后缀数组(suffix array)详解 - 北岛知寒 - 博客园 (cnblogs.com)
前置知识
子串
字符串S的子串r[i..j],i<=j,表示S串中从i到j这一段,就是顺次排列r[i],r[i+1],...,r[j]形成的子串。
后缀
后缀是指从某个位置 i 开始到整个串末尾结束的一个特殊子串。字符串r的从第i个字符开始的后缀表示为Suffix(i),
也就是Suffix(i)=S[i...len(S)-1] 。
后缀数组(SA[i]存放排名第i大的后缀首字符下标)
后缀数组 SA 是一个一维数组,它保存1..n 的某个排列SA[1] ,SA[2] ,...,SA[n] ,
并且保证Suffix(SA[i])<Suffix(SA[i+1]), 1<=i<n 。
也就是将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA 中。
名次数组(rank[i]存放suffix(i)的优先级)
名次数组 Rank[i] 保存的是 Suffix(i) 在所有后缀中从小到大排列的“名次”
1 | 理解为sa[i]就是第i小的那个字符串起始的那个位置 |
后缀数组 最详细讲解 - victorique - 博客园 (cnblogs.com)
盗窃一下这位大师的图,先orz奉上
倍增过程
初始化
首先对字符串 $ s $ 的所有长度为 1 的子串(即每个字符)进行排序,得到排序后的编号数组 $ _1 $ 和排名数组 $ _1 $。
倍增步骤
- 长度为 2 的子串
- 用两个长度为 1 的子串的排名,即 $ _1[i] $ 和 $ _1[i+1]
$,作为排序的第一、第二关键字。
- 对字符串 $ s $ 的每个长度为 2 的子串:
$ {s[i (i+1, n)] | i } $
进行排序,得到 $ _2 $ 和 $ _2 $。
- 用两个长度为 1 的子串的排名,即 $ _1[i] $ 和 $ _1[i+1]
$,作为排序的第一、第二关键字。
- 长度为 4 的子串
- 用两个长度为 2 的子串的排名,即 $ _2[i] $ 和 $ _2[i+2]
$,作为排序的第一、第二关键字。
- 对字符串 $ s $ 的每个长度为 4 的子串:
$ {s[i (i+3, n)] | i } $
进行排序,得到 $ _4 $ 和 $ _4 $。
- 用两个长度为 2 的子串的排名,即 $ _2[i] $ 和 $ _2[i+2]
$,作为排序的第一、第二关键字。
- 一般情况
- 用长度为 $ $ 的子串的排名,即 $ {w/2}[i] $ 和 $
{w/2}[i+w/2] $,作为排序的第一、第二关键字。
- 对字符串 $ s $ 的每个长度为 $ w $ 的子串:
$ s[i (i+w-1, n)] $
进行排序,得到 $ _w $ 和 $ _w $。
- 其中,当 $ i+w > n $ 时,视 $ _w[i+w] $ 为无穷小。
- 最终,当 $ w n $ 时,得到的编号数组 $ _w $ 就是后缀数组。
- 用长度为 $ $ 的子串的排名,即 $ {w/2}[i] $ 和 $
{w/2}[i+w/2] $,作为排序的第一、第二关键字。
时间复杂度分析
- 倍增次数
- 倍增的过程是 $ O(n) $,因为每次长度翻倍,最终不超过字符串长度 $ n $。
- 排序复杂度
- 每次倍增使用
sort
对子串进行排序,复杂度为 $ O(n n) $。
- 每次排序中,子串的比较花费 $ 2 $ 次字符比较。
- 每次倍增使用
- 更新排名
- 每次倍增后,需要 $ O(n) $ 时间更新排名 $ $。
- 相较于 $ O(n n) $ 的排序复杂度,可以忽略不计。
- 每次倍增后,需要 $ O(n) $ 时间更新排名 $ $。
综合来看,算法的时间复杂度为: $ O(n ^2 n) $
优化方向
如果能够实现 $ O(n) $ 的排序,那么可以在 $ O(n n) $ 的时间内计算后缀数组。
基数排序
假设有一堆3位数
先按照个位数扔进去桶
再依次从左到右出来
然后按照十位数一个个扔进去桶
再依次从左到右出来
再百位
最后出来的一定是有序的。
又到了经典的大眼瞪小眼环节()
完整代码:
1 |
|
LCP
1 | void LCP() |