SA(后缀数组)学习笔记

虽然是学习笔记但其实是复习

本文只讲述后缀数组的倍增法实现. 如果后续学了\(DC3\)实现就补上.

后缀数组用于获得一个字符串的所有后缀的字典序排序. 而这个排序我们用倍增实现.

废话就不多说了, 这两个神犇讲的很好的(神犇1 神犇2), 我这里做一个总结用于个人后续复习.

\(Suffix[i]\) : \(s[i],s[i+1]...s[n]\) 组成的字符串.

\(SA[i]\) : 在所有 \(Suffix\) 中, 字典序排名为 \(i\)\(Suffix[j]\) 其下标 \(j\)

\(rank[i]\) : \(Suffix[i]\) 在所有 \(Suffix\) 中的字典序排名. 和 \(SA[i]\) 为逆操作, 即 \(rank[sa[i]]=i , sa[rank[i]]=i\)

\(height[i]\) : 排名为 \(i\) 的后缀 \(Suffix[sa[i]]\) 和排名为 \(i-1\) 的后缀 \(Suffix[sa[i-1]]\)

\(H[i]\) : \(height[rank[i]]\) , 即 \(Suffix[i]\) 和它字典序前一名(不妨设为 \(Suffix[j]\) , 即 \(rank[i]=rank[j]+1\) )的最长公共前缀.

这里有个结论 \(H[i] \ge H[i-1]-1\) , 证明参考神犇2.
证明 : 设 \(Suffix[i-1]\) 的字典序前一名下标为 \(k\) , 即 \(SA[rank[i-1]-1]=k\)

  • 如果 \(Suffix[i-1]\)\(Suffix[k]\) 第一个字符就不一样 , 即 \(H[i-1] = 0\) 那没什么好说的 , 显然 \(H[i] \ge 0\) ;
  • 如果 \(Suffix[i-1]\)\(Suffix[k]\) 第一个字符一样 , 那么我们可以发现 \(Suffix[i]\)\(Suffix[k+1]\) 的最长公共前缀一定为等于 \(H[i-1]-1\) , 因为都在 \(H[i-1]\) 的基础上去掉了第一个字符 . 然后考虑\(Suffix[i]\)\(Suffix[k+1]\) 的最长公共前缀, 即 \(LCP(Suffix[k+1],Suffix[i])\)\(H[i]\) 的关系 (注意, 这里 \(Suffix[k+1]\) 不一定是 \(Suffix[i]\) 字典序的前一名) , 因为 \(Suffix[k]\) 的字典序排在 \(Suffix[i-1]\) 的前面 , 在同时去掉第一个相同的字符之后得到的 \(Suffix[k+1]\) 字典序也一定比 \(Suffix[i]\) 小 , 即 \(rank[k+1] \le rank[i]-1\) , 然后由于 \(LCP(Suffix[k+1],Suffix[i]) = min ( height[rank[k]+1] , height[rank[k]+2] ...height[rank[i]])\) , 得到 \(H[i-1]=LCP(Suffix[k+1],Suffix[i]) \le height[rank[i]] = H[i]\)

剩余实现细节见代码注释

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1e6+10;
int n; // 字符串长度
int sa[maxn];
int rk[maxn];
int height[maxn];
int tp[maxn]; // 用于辅助排序的, 指按照第二关键字排序之后 , 排名为i的下标
int tax[maxn]; // 基数排序的桶
int betnum=0; // 基数排序有多少桶
char ss[maxn]; 
void sasort()
{
    // 按照第一关键字放桶里 然后前缀和 按照第二关键字倒序取出
    for(int i=1;i<=betnum;i++) tax[i]=0;
    for(int i=1;i<=n;i++) tax[rk[i]]++;
    for(int i=2;i<=betnum;i++) tax[i]+=tax[i-1];
    for(int i=n;i>=1;i--) sa[tax[rk[tp[i]]]--]=tp[i];
}
void initsa()
{
    n=strlen(ss+1); betnum=122;
    for(int i=1;i<=n;i++)
        rk[i]=ss[i],tp[i]=i;
    sasort();
    for(int len=1,p=0;p<n;len<<=1) // 如果分出来n个不同的就可以直接结束了
    {
        p=0;
        for(int i=1;i<=len;i++) tp[++p]=n-len+i;
        for(int i=1;i<=n;i++) if(sa[i]>len) tp[++p]=sa[i]-len;
        sasort(); 
        swap(tp,rk); // 更新rank了 但是需要用之前的rank 所以把没有用的tp数组扔掉
        p=0; 
        rk[sa[1]]=p=1;
        for(int i=2;i<=n;i++)
            if(tp[sa[i]]==tp[sa[i-1]] && tp[sa[i]+len]==tp[sa[i-1]+len])
                rk[sa[i]]=p;
            else 
                rk[sa[i]]=++p;
        betnum=p;
    }
    int preh=0;
    for(int i=1;i<=n;i++)
    {
        if(preh) preh--; int j=sa[rk[i]-1];
        while(ss[i+preh]==ss[j+preh]) preh++;
        height[rk[i]]=preh;
    }
}
int main()
{
    scanf("%s",ss+1);
    initsa();
    for(int i=1;i<=n;i++) printf("%d ",sa[i]);
    return 0;
}
posted @ 2021-10-30 19:57  Junble  阅读(212)  评论(0)    收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css