后缀数组——倍增算法

后缀数组,是指对给定序列(可以是字符串或者普通的数字数组,甚至可以是某些任意带权值的可比较数据),所有的后缀的排序,数据结构方面,主要包括两个数组:sa[i],rank[i],sa[i]记录当前排到第i的是谁,其实存储的就是其他的排序(如快排或者插入排序这一类依靠比较的排序算法)的最后结果,rank[i]则表示i这个字符开始的子串排第几,在这里,得到sa[i]利用了计数排序,关于计数排序,所以先介绍一下计数排序:

计数排序所用到的数据结构:a[i]表示原始的序列,c[i]用来存储中间结果,sa[i]存储最后的结果,计数排序开始时需要用c[i]来存储数字i出现了几次,所以要求数据的范围不能过大,否则数组有可能开不下。

算法过程:先得到一个数i在序列中出现的次数c[i],进而可以得到>=i的数有多少个,同样记录在c[i]中,这样就可以确定一个数应该出现在什么位置上了,代码如下:

 1 int a[1000],b[1000],c[1000];
 2 void counting_sort(int n)
 3 {
 4     memset(c,0,sizeof(c));
 5     for(i = 1;i <= n; i++)
 6         c[a[i]]++;//此时c[i]存储i出现的次数
 7     for(i = 2;i <= n; i++)
 8         c[i] += c[i - 1];//存储>=i的数字个数
 9     for(j = n;j > 0 ; j--)10         b[c[a[j]]--] = a[j];//得到排第b[c[a[j]]]的是a[j]
11 }

在后缀数组的倍增算法中,每次得到从i处起始2^k个字符的排序,2^k可以通过相连的两个2^(k-1)个字符的排序情况得到,我们可以设定超过字符串长度的部分值为0,在得到2^k长度子串的排序情况时,假设相连的两个2^(k-1)子串的排序情况分别为x,y则可以利用基数排序(注意这里不是计数排序,要搞清楚)的原理,把这2^k个字符的值定位xy,如x=10,y=20,则xy=1020,对aabaaaab的排序过程:

代码如下:

void sorting(int j)//基数排序
{
    memset(sum,0,sizeof(sum));
    for (int i=1; i<=s.size(); i++) sum[ rank[i+j] ]++;
    for (int i=1; i<=maxlen; i++) sum[i]+=sum[i-1];
    for (int i=s.size(); i>0; i--) tsa[ sum[ rank[i+j] ]-- ]=i;//对第二关键字计数排序,tsa代替sa为排名为i的后缀是tsa[i] 

    memset(sum,0,sizeof(sum));
    for (int i=1; i<=s.size(); i++) sum[ rank[i] ]++;
    for (int i=1; i<=maxlen; i++) sum[i]+=sum[i-1];
    for (int i=s.size(); i>0; i--) sa[ sum[ rank[ tsa[i] ] ]-- ]= tsa[i]; //对第一关键字计数排序
    //构造互逆关系 
}

void get_sa()
{
    int p;
    for (int i=0; i<s.size(); i++) trank[i+1]=s[i];
    for (int i=1; i<=s.size(); i++) sum[ trank[i] ]++;
    for (int i=1; i<=maxlen; i++) sum[i]+=sum[i-1];
    for (int i=s.size(); i>0; i--) 
        sa[ sum[ trank[i] ]-- ]=i;
    rank[ sa[1] ]=1;
    for (int i=2,p=1; i<=s.size(); i++)
    {
        if (trank[ sa[i] ]!=trank[ sa[i-1] ]) p++;
        rank[ sa[i] ]=p;
    }//第一次的sa与rank构造完成
    for (int j=1; j<=s.size(); j*=2)
    {
        sorting(j);
        trank[ sa[1] ]=1; p=1; //用trank代替rank 
        for (int i=2; i<=s.size(); i++)
        {
            if ((rank[ sa[i] ]!=rank[ sa[i-1] ]) || (rank[ sa[i]+j ]!=rank[ sa[i-1]+j ])) p++;
            trank[ sa[i] ]=p;//空间要开大一点,至少2倍
        }
        for (int i=1; i<=s.size(); i++) rank[i]=trank[i];
    }
}
原文地址:https://www.cnblogs.com/caozhenhai/p/2962243.html