字符串——后缀数组
后缀数组
记字符串 S S S 的后缀字符串 s i s_i si 为 S [ i … n ] S[i \ldots n] S[i…n] 即以 i i i 为起点的后缀,将所有的后缀字符串按照字典序进行排序,得到后缀数组,后缀数组主要包含两个:
- s a [ i ] sa[i] sa[i] 排名为 i i i 的后缀字符串的起点。
- r k [ i ] rk[i] rk[i] 以 i i i 为起点的后缀字符串的排名。
后缀排序
一种朴素的后缀排序算法的时间复杂度是 O ( n 2 log n ) O(n^2 \log n) O(n2logn) 的,通过使用一种倍增的思想,我们能够实现 O ( n log 2 n ) O(n \log^2 n) O(nlog2n) 的后缀排序算法。
第一步我们先处理所有子串长度为 1 1 1 的 r k rk rk 数组的排名,即字符的 ASCII 值本身,可以写作 r k [ i ] = s t r [ i ] rk[i] = str[i] rk[i]=str[i] ,接下来我们倍增进行扩大子串的长度为 2 , 4 , 8 , 16 , … 2,4,8,16,\ldots 2,4,8,16,… ,我们已知 子串长度为 w w w 的 s a sa sa 和 r k rk rk 数组,我们可以通过双关键字排序求得子串长度为 2 w 2w 2w 的 s a sa sa 和 r k rk rk 数组。长度不足的子串使用空字符进行不全,易知在字符串尾部添加空字符串并不影响后缀子串的排名。
那么问题就转换成对字符串组 A B AB AB 和 C D CD CD 进行比较大小,其中 A , B , C , D A,B,C,D A,B,C,D 都是长度为 w w w 的子串,很显然,我们先比较 A A A 和 C C C 的大小,如果相等再比较 B B B 和 D D D 的大小即可,即进行双关键字比较。
根据上面的思路,我们的第一版代码为:
char str[1000005];
int sa[1000005];
int rk[1000005];
int nrk[1000005];
int cnt[1000005];
int idk[1000005];
void suffix_sort(int n)
{
for (int i = 1; i <= n; i++)
{
rk[i] = str[i];
sa[i] = i;
}
for (int w = 1; w <= n; w <<= 1)
{
sort(sa + 1, sa + 1 + n, [&](int a, int b){
if(rk[a] == rk[b]) return rk[a + w] < rk[b + w];
return rk[a] < rk[b];
});
for (int i = 1, p = 0; i <= n; i++)
{
if (rk[sa[i]] == rk[sa[i - 1]] && rk[sa[i] + w] == rk[sa[i - 1] + w])
{
nrk[sa[i]] = p;
}
else
{
nrk[sa[i]] = ++p;
}
}
memcpy(rk, nrk, sizeof(rk));
}
}
因为我们的排序值域较小,我们可以使用基数排序优化这个过程,下面的代码时间复杂度为 O ( n log n ) O(n \log n) O(nlogn) 。
char str[1000005];
int sa[1000005];
int rk[1000005];
int nrk[1000005];
int cnt[1000005];
int idk[1000005];
// 后缀排序
void suffix_sort(int n)
{
for (int i = 1; i <= n; i++)
{
rk[i] = str[i];
sa[i] = i;
}
for (int w = 1; w <= n; w <<= 1)
{
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
cnt[rk[sa[i] + w]]++;
for (int i = 1; i <= 1000000; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
idk[cnt[rk[sa[i] + w]]--] = sa[i];
memcpy(sa, idk, sizeof(sa));
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++)
cnt[rk[sa[i]]]++;
for (int i = 1; i <= 1000000; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
idk[cnt[rk[sa[i]]]--] = sa[i];
memcpy(sa, idk, sizeof(sa));
for (int i = 1, p = 0; i <= n; i++)
{
if (rk[sa[i]] == rk[sa[i - 1]] && rk[sa[i] + w] == rk[sa[i - 1] + w])
{
nrk[sa[i]] = p;
}
else
{
nrk[sa[i]] = ++p;
}
}
memcpy(rk, nrk, sizeof(rk));
}
}
LCP
待补充