后缀数组,是指对给定序列(可以是字符串或者普通的数字数组,甚至可以是某些任意带权值的可比较数据),所有的后缀的排序,数据结构方面,主要包括两个数组: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]; } }