后缀数组

先介绍计数排序。思考一下桶排序,桶排序是不稳定的。计数排序相当于是稳定的桶排序,时间复杂度为O()

设数组a的值域为[1,n],数组c表示每个元素的数目(也就是桶),数组r[i]表示a[i]的排名(注意这个排名是稳定的,也就是说当有多个a[i]的时候,相对顺序不会变化),然后对c求前缀和,这样c[i]就表示小于等于i的数有多少个(下面的c都表示已经求了前缀和的c)。然后倒序循环数组a,循环到a[i]的时候,令r[i]=c[a[i]],并令c[a[i]]减一

正确性:假设有若干个k,我们现在只关心k。那么我们知道,最终排完序的a数组,严格小于k的有c[k1]个,也就是说k的排名最低是c[k1]+1,最高是c[k];而由于要稳定,所以原数组a中最后一个k的排名是c[k],最前面的一个k的排名是c[k1]+1;我们倒序循环,由于c求了前缀和,上述过程刚好帮我们完成了排序工作

接下来进入正题,介绍后缀数组。下文默认字符串下标从1开始,那么长度为n的字符串s一共有n个后缀,第i个后缀就是s[i...n]

后缀数组涉及三个数组:sa,rk,heightsa[i]表示s所有后缀中,排名为i的后缀是第几个后缀;rk[i]表示s的第i个后缀的排名;height[i]表示排名第i位的后缀与排名第i1位的后缀的最长公共前缀(lcp),特别有height[1]=0

然后先求sa。下文的x[i]表示按照前k个字母排序的前提下,s的第i个后缀的离散化之后的值(若不同后缀的前k个字母相同,则离散化之后的值相同),m表示离散化值域,y[i]数组表示排名为i的是第几个后缀(最后也会作为辅助数组)

第一步:将所有后缀按照第一个字母稳定地排序(利用计数排序)

for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
//这里由于字符集不大,所以桶的大小直接开成字符集大小,离散化按照字符离散化
for(int i=2;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i;i--) sa[c[x[i]]--]=i;

第二步:假设我们已经对所有后缀按照前k个字母稳定地排好了序,我们接下来将所有后缀按照前2k个字母稳定地排序。

外层循环:for (int k = 1; k <= n; k <<= 1)

我们先按照所有后缀的第k+1 ~ 2k个字母排序(这里不要求稳定),然后再按照所有后缀的第1 ~ k个字母排序(这里要求稳定);由于不可能存在两个后缀相同,所以经过上面两次排序之后,得到的sa一定是按所有后缀按照前2k个字母稳定地排序得到的

先进行第一次排序:

对于i[nk+1,n],s[i...n]是没有前2k个字母的,由于空字符串的字典序小于任何字符串,所以我们直接按照顺序将其放入y中即可

int num = 0;
for (int i = n - k + 1; i <= n; i ++ ) y[ ++ num] = i;

对于i[1,nk],s[i...n]的第k+1 ~ 2k个字母就是s[i+k...i+2k](长度不够的用空字符串补齐),于是比较s[i...n]的第k+1 ~ 2k个字母就是比较s[i+k...i+2k],也就是s的第i+k个后缀的前k个字母,而此时我们的sa[i]刚好记录了按照前k个字母排序,排名为i的后缀,于是我们从小到大循环i,若sa[i]>k,则将sa[i]k放入y数组即可

for (int i = 1; i <= n; i ++ )
if (sa[i] > k) y[ ++ num] = sa[i] - k;

再进行第二次排序:

首先按照前k个字母进行计数排序

for (int i = 1; i <= m; i ++ ) c[i] = 0;
for (int i = 1; i <= n; i ++ ) c[x[i]] ++ ;
for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
for (int i = n; i; i -- ) sa[c[x[y[i]]] -- ] = y[i];

然后将y作为辅助数组记录所有后缀按照前k个字母排序的离散化数值

swap(x, y);

然后更新x,让x[i]表示s的第i个后缀按照前2k个字母排序的离散化数值(注意此时y[i]表示的是s的第i个后缀按照前k个字母排序的离散化数值,所以s的第i个后缀的第k+1 ~ 2k个字母就是s的第i+k个后缀的前k个字母)

x[sa[1]] = 1, num = 1;
for (int i = 2; i <= n; i ++ )
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) 
x[sa[i]] = num;
else x[sa[i]]=++num;

注意y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]是不会越界的,因为

image

最后,如果离散化值域达到了n,则说明排序完成,否则的话令m为离散化值域重复上述过程

if (num == n) break;
m = num;

上述倍增法的时间复杂度为O(nlogn)

height见OI-wiki

补充一个lcp的性质:设i<j,则lcp(i,j)=mink=ij(lcp(i,k),lcp(k,j)),其中k是任取的(证明见视频00:37:20);由这个性质不难得出,如果想求lcp(i,j)的话,求一下lcp(i,i+1),lcp(i+1,i+2),...,lcp(j1,j)并取最小值就好了

posted @   最爱丁珰  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示