后缀数组入门学习(1):倍增算法基础

满心欢喜来了后缀数组,但是发现自己很懵……

后缀数组倍增算法的板子并不长,但理解起来相对很困难。

后缀的含义自然不用说了……为了简洁,我们设后缀i为从第i位开始的后缀。

需要这样几个数组:

char s[],存字符串(废话),int sa[](本体),xx[](中转数组,存rank),yy[](中转数组,存编号),c[](基排用)

然后我们需要预备知识:基数排序

具体理论就不说了,可以参考百度百科;

形象一点:找了一堆桶,把每个数/字符放在对应的桶里,然后按桶的编号挨个取出来,排好,就是基数排序。

在代码实现上c数组就是我们的桶,c[i]表示“字符值小于等于i的字符个数”

而对于字符值恰好等于i的那个字符,它的排名就是c[i](由于它是小于等于i的最大一个字符),然后我们把c[i]-=1即可

代码长这样:

1 memset(c,0,sizeof(c));
2 for(int i=0;i<n;i++)c[x[i]=s[i]]++;//这里是一开始初始化赋值的地方
3 for(int i=1;i<m;i++)c[i]+=c[i-1];
4 for(int i=n-1;~i;i--)sa[--c[x[i]]]=i;

接下来,我们就可以介绍本体代码了:

首先,我们对每个字符排序,这样我们会得到每个字符的排名。可能有同学有疑问,按照基排的话,相同字母的排名就不同了怎么办?后面我们还会有去重的部分,所以我们中转数组这样不重复的打也没事。在这之后,我们给每个后缀的前两个字符排序(没有第二个字符的按照0处理,0最小,以后也是这样操作)。这相当于给一些二元组排序。利用我们刚刚第一次给字母排序的结果,很容易实现这次排序(我们在从第二次开始每次排序后面都会进行一次去重)。

这之后,我们对每个后缀的前4个字符排序。由于对于后缀i和后缀j,给他们的前4个字符排序相当于“比较i和j的前两个字符”和“比较i+2和j+2的前两个字符”,而我们已经给前两个字符排好序,所以这样就可以利用前面排序的结果来操作。

不断这样操作,当每个后缀的排名都不同时,就可以跳出了。

代码见下:

 1 char s[N];
 2 int sa[N],rank[N],xx[N],yy[N],c[N],n;
 3 inline void get_sa(int m)//m为最大的字符值
 4 {
 5     int *x=xx,*y=yy;
 6     memset(c,0,sizeof(c));
 7     for(int i=0;i<n;i++)c[x[i]=s[i]]++;
 8     for(int i=1;i<m;i++)c[i]+=c[i-1];
 9     for(int i=n-1;~i;i--)sa[--c[x[i]]]=i;
10     for(int k=1,p=0;k<=n;k<<=1,p=0)
11     {
12         for(int i=n-k;i<n;i++)y[p++]=i;
13         for(int i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
14         memset(c,0,sizeof(c));
15         for(int i=0;i<n;i++)c[x[y[i]]]++;
16         for(int i=1;i<m;i++)c[i]+=c[i-1];
17         for(int i=n-1;~i;i--)sa[--c[x[y[i]]]]=y[i];
18         swap(x,y);
19         p=1,x[sa[0]]=0;
20         for(int i=1;i<n;i++)
21             x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])?p-1:p++;
22         if(p>=n-1)return;m=p;
23     }
24 }
原文地址:https://www.cnblogs.com/LadyLex/p/7112103.html