一 点睛
在字符串处理中,后缀树和后缀数组都是非常有力的工具,后缀数组是后缀树的一个非常精巧的替代品,比后缀树更容易实现,可以实现后缀树的很多功能,时间复杂度也不逊色,比后缀树所占用的空间也小很多。
一个长度为 8 的字符串 aabaaaab,一共有 8 个后缀,后缀的编号是后缀在原串中的起始位置。把 8 个后缀按字典序升序排序。
二 相关概念
1 后缀
指从某个位置开始到字符串末位的一个特殊子串。字符串 s 从第 i 个字符开始的后缀被表示为 Suffix(i),也可以称为下标为 i 的后缀。字符串 s='aabaaaab',其所有的后缀如下:
Suffix(0)="aabaaaab"
Suffix(1)="abaaaab"
Suffix(2)="baaaab"
Suffix(3)="aaaab"
Suffix(4)="aaab"
Suffix(5)="aab"
Suffix(6)="ab"
Suffix(7)="b"
2 后缀数组
将所有的后缀都从小到大排序,将排好序的后缀下标 i 放入数组中,该数组就叫作后缀数组。将上面所有后缀都按字典序排序之后,取其下标 i,即可得到后缀数组。
Suffix(3)="aaaab"
Suffix(4)="aaab"
Suffix(5)="aab"
Suffix(0)="aabaaaab"
Suffix(6)="ab"
Suffix(1)="abaaaab"
Suffix(7)="b"
Suffix(2)="baaaab"
后缀数组SA={3,4,5,0,6,1,7,2}
3 排名数组
排名数组指下标为 i 的后缀排序后的名次,例如在上面例子中排序后的下标和名次。若 rank[i]=num,则下标为 i 的后缀排序后的名次为 num:
名次 下标 后缀
num i Suffix(i)
1 3 aaaab
2 4 aaab
3 5 aab
4 0 aabaaaab
5 6 ab
6 1 abaaaab
7 7 b
8 2 baaaab
下标为 3 的后缀,排名第 1;排名第 1 的后缀,下标为 3,即 SA[1]=3。排名数组和后缀数组是互逆的,可以来回转换。
4 高度数组
height[i] = lcp(sa[i],sa[i-1])
第 i 名后缀与第 i-1 名后缀的最长公共前缀的长度。
height[2]=lcp(5,4)=3
height[3]=lcp(6,5)=2
height[4]=lcp(1,6)=3
......
高度数组表示两个后缀的相似度,排序相邻的两个后缀相似度最高。
后缀 i 的前邻后缀一定是 sa[rk[i]-1],因为 i = sa[rk[i]],i 的排名为 rk[i],排名减1取 sa 即得。
sa[rk[1]-1]=sa[4-1]=sa[3]=6。
定理:
height[rk[i]]>=height[rk[i-1]]-1
三 后缀数组的构建思路
1 将字符串 s(aabaaaab)从每个下标开始长度为 1 的子串进行排名,直接将每个字符转换成 s[i]-'a'+1即可,如下图所示。
2 求解长度为 2 的子串排名。
将上一次 rank 值的第 i 个和 第 i+1 个结合,相对于得到长度为 2 的子串的每个位置排名,然后排序,即可得到长度为 2 的子串排名。
3 求解长度为 2^2 的子串排名。
将上一次 rank 值的第 i 个和第 i+2 个结合,相对于得到长度为 2^2 的子串的每个位置排名,排序后得到长度为 2^2 的子串排名。
4 求解长度为 2^3 的子串排名。
将上一次 rank 值的第 i 个和第 i+4 个结合,相对于得到长度为 2^3 的子串的每个位置排名,排序后得到长度为 2^3 的子串排名。
第 4 步和第 3 步的结果一样,实际上,若在 rank 没有相同值时,就已经得到了后缀排名,就不需要再继续运算了。因为根据字符串比较规则,两个字符串的前几个字符已经分出大小,后面无须判断。
将排名数组转换为后缀数组,排名第1的下标为3,排名第2的下标为4,排名第3的下标为5,排名第4的下标为0,排名第5的下标为6,排名为6的下标为1,排名第7的下标为7,排名第8的下标为2,因此SA[]={3,4,5,0,6,1,7,2}。