您的位置:首页 > 其它

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

2017-07-03 17:19 513 查看
满心欢喜来了后缀数组,但是发现自己很懵……

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

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

需要这样几个数组:

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

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

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

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

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

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

代码长这样:

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


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

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

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

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

代码见下:

char s
;
int sa
,rank
,xx
,yy
,c
,n;
inline void get_sa(int m)//m为最大的字符值
{
int *x=xx,*y=yy;
memset(c,0,sizeof(c));
for(int i=0;i<n;i++)c[x[i]=s[i]]++;
for(int i=1;i<m;i++)c[i]+=c[i-1];
for(int i=n-1;~i;i--)sa[--c[x[i]]]=i;
for(int k=1,p=0;k<=n;k<<=1,p=0)
{
for(int i=n-k;i<n;i++)y[p++]=i;
for(int i=0;i<n;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
memset(c,0,sizeof(c));
for(int i=0;i<n;i++)c[x[y[i]]]++;
for(int i=1;i<m;i++)c[i]+=c[i-1];
for(int i=n-1;~i;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1,x[sa[0]]=0;
for(int i=1;i<n;i++)
x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k])?p-1:p++;
if(p>=n-1)return;m=p;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: