码迷,mamicode.com
首页 > 编程语言 > 详细

后缀数组

时间:2019-08-23 19:31:49      阅读:104      评论:0      收藏:0      [点我收藏+]

标签:技术   csdn   cpp   代码   怎么   存在   而且   简单   并且   

题目很简单,就是给出一个字符串,把这个字符串的所有非空后缀从小到大排序后,按顺序输出后缀的第一个字符在原串中的位置。

样例

输入样例:

ababa

输出样例:

5 3 1 4 2

解释:

排好序后为:

  1. a

  2. aba

  3. ababa

  4. ba

  5. baba

暴力肯定不行啦!
这里我们使用一个叫后缀数组的东西

PS:搞懂模版还要感谢大佬zhangjianweivv的博客!

什么是后缀数组?

后缀数组为rank[i],为Suffix array的简写,表示排名为i的字符串的 编号 (PS:这里,我们简称排序后后缀的第一个字符所在的位置为 编号

还有一个玩意儿叫名次数组,为rank[i],表示编号为i的字符串的 排名

可以发现,rank[i]sa[i]的逆运算,而且我们所需要的答案就是sa[i]

后缀数组怎么求?

这才是真正的步入主题啊。。ps:前面讲辣么多废话

这里我们讲倍增算法,因为好理解我菜

另当前的字符串为aabaaaab

下面这个图很清楚:

技术图片

图片来源:《后缀数组--处理字符串有力工具》

  • 我们先把字符串的每个字母排序,存在rank数组里面,我们认为这是第一关键字

  • 然后我们将第一关键字每隔$2^k$的字符的排名作为第二关键字
  • 这时,我们使用计数排序(网上很多都说是基数排序,但其实是计数排序)将这两个关键字排序,就类似于两位数字的排序
  • 将排序后的结果作为第一关键字,然后继续循环第二,三次操作

计数排序是什么?

计数排序和基数排序很像

先讲基数排序:
技术图片
图片来源:jinkun113大佬的博客

类似于桶排序,

  • 先按个位的数字一个个放入0~9的桶里,再按顺序从0~9取出来,(当然如果你是从大到小就是9~0啦!)

  • 接着再按十位的数字一个个放入桶里,按顺序取出来,如果是两位数,这时我们得到的就是答案!
  • 以此类推,一直循环到最后一位

计数排序,就更高级了。这里优化了一下,
就是我们在基数排序所用的桶,是记录他出现的次数,然后累加(写入代码就是cnt[i]+=cnt[i-1]),这样我们得到的cnt[i]数组里就是不大于i的个数,就是他的排名了!

上代码!

我先解释一下每个变量的含义

rank[i]表示编号为i的排名 
sa[i]表示排名为i的编号 
cnt[i]计数排序的桶 
pos[i]表示当前第二关键字已经排好序时第i名第二关键字所对应的第一关键字位置
tmp[i]
排序时:表示当前排序中编号为i的排名
排序后:表示调整rank前的排名
#include<cstdio>
#include<cstring>
using namespace std;
/*
rank[i]表示编号为i的排名 
sa[i]表示排名为i的编号 
cnt[i]计数排序的桶 
pos[i]表示当前第二关键字已经排好序时第i名第二关键字所对应的第一关键字位置
tmp[i]
排序时:表示当前排序中编号为i的排名
排序后:表示调整rank前的排名
*/
char s[1200000];
int cnt[1200000],pos[1200000],sa[1200000],tmp[1200000],rank[1200000];
bool pd(int x,int y,int k){return tmp[x]==tmp[y]&&tmp[x+k]==tmp[y+k];}
void suffix(int n,int m)
{
    int i,k;
    for(i=1;i<=n;i++)rank[i]=s[i],cnt[rank[i]]++;
    for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
    for(i=n;i>=1;i--)sa[cnt[rank[i]]--]=i;//计数排序,设置好rank和sa数组 
    for(k=1;k<n;k<<=1)//k表示长度,k<<1表示k*2 
    {
        int len=0;for(i=n-k+1;i<=n;i++)pos[++len]=i;//如果第二关键字为0的话,肯定最小,我们先加入pos 
        for(i=1;i<=n;i++)if(sa[i]>k)pos[++len]=sa[i]-k;//准备好pos数组 
        
        memset(cnt,0,sizeof(cnt));
        for(i=1;i<=n;i++)tmp[i]=rank[pos[i]],cnt[tmp[i]]++;
        for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
        for(i=n;i>=1;i--)sa[cnt[tmp[i]]--]=pos[i];//更新sa数组 
        
        for(i=1;i<=n;i++)tmp[i]=rank[i];//记录之前的排名 
        
        len=1;rank[sa[1]]=1;//初始化 
        for(i=2;i<=n;i++){if(pd(sa[i],sa[i-1],k)==false)len++;rank[sa[i]]=len;}//通过sa来更新rank数组,并且去重 
        if(len==n)break;m=len; 
    }
    for(i=1;i<n;i++)printf("%d ",sa[i]);
    printf("%d\n",sa[n]);
}
int main()
{
    scanf("%s",s+1);int len=strlen(s+1);
    suffix(len,130);//'z'是122 ,为了保险设置为130
    return 0;
}

后缀数组

标签:技术   csdn   cpp   代码   怎么   存在   而且   简单   并且   

原文地址:https://www.cnblogs.com/candy067/p/11401946.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!