用倍增法构造后缀数组中的SA及RANK数组

感觉后缀数组很难学的说= = 不过总算是啃下来了

首先 我们需要理解一下倍增法构造的原理

设原串的长度为n 对于每个子串 我们将它用‘\0‘补成长度为2^k的串(2^k-1<n<=2^k)

比如串aba的子串就有 aba‘\0‘    ba‘\0‘‘\0‘  a‘\0‘‘\0‘‘\0‘

每次操作我们可以排出所有长度为 2^x的子串的大小

比如串aba的排序过程

第一遍 a                   a             b

第二遍 a‘\0‘             ab           ba

第三遍 a‘\0‘‘\0‘‘\0‘   aba‘\0‘    ba‘\0‘‘\0‘

理解这些后 我们可以先写一个 nlog^2n的快排实现的方法

这种方法比较好写 如果n<=10^5就放心地去用吧

//SA nlog^2n
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define rep(i,n) for(int i=1;i<=n;++i)
#define imax (x>y?x:y)
#define imax (x<y?x:y)
using namespace std;
const int N=100010;
struct node
{
    int x,y,ma;
}tr[N];
char ch[N];
int r[N<<1],sa[N];
int n;
bool cmp(node aa,node bb)
{
    return aa.x<bb.x||(aa.x==bb.x&&aa.y<bb.y);
}
void getsa()
{
    for(int i=1;1<<(i-1)<n;++i)
    {
        rep(j,n)
        {
            tr[j].x=r[j];
            tr[j].y=r[j+(1<<i-1)];
            tr[j].ma=j;
        }
        sort(tr+1,tr+1+n,cmp);
        int cnt=0;
        rep(j,n)
        r[tr[j].ma]=tr[j].x==tr[j-1].x&&tr[j].y==tr[j-1].y?cnt:++cnt;
    }
    rep(j,n)
    sa[r[j]]=j;
}
int main()
{
    scanf("%s",ch+1);
    n=strlen(ch+1);
    rep(i,n)
    r[i]=ch[i];
    getsa();
    printf("RANK: ");
    rep(i,n)
    printf("%d ",r[i]);
    printf("\nSA:   ");
    rep(i,n)
    printf("%d ",sa[i]);
    return 0;
}

然而 考虑到rank数组的特殊性(一定<=n) 我们还可以使用基数排序把复杂度降到nlogn

这样就可以解决n<=10^6的问题啦

然而这个的确比较容易写错 并且需要先掌握基数排序的原理

基数排序从直观上是需要链表去做的 然而只用一个数组也同样可以很方便的实现

具体可以参考下代码

//SA nlogn(n=1需要特判下 这里懒得写了)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define rep(i,n) for(int i=1;i<=n;++i)
#define imax (x>y?x:y)
#define imax (x<y?x:y)
using namespace std;
const int N=1000010,S=128;//通常字符都是在0-127之间的
char ch[N];
int sum[S],r[2][N<<1],sa[2][N];
int n,t;
void getsa(int i)
//这里面的SA值并非最后的SA值 但保证对应rank值相等的一定相邻 从而方便比较大小
{
    memset(sum,0,sizeof(sum));
    rep(j,n)
    ++sum[r[t][j+i]];
    for(int j=1;j<S;++j)
        sum[j]+=sum[j-1];
    rep(j,n)
    sa[0][sum[r[t][j+i]]--]=j;
    memset(sum,0,sizeof(sum));
    rep(j,n)
    ++sum[r[t][j]];
    for(int j=1;j<S;++j)
        sum[j]+=sum[j-1];
    for(int j=n;j;--j)
    //基数排序从第二次排序开始是一定要倒序找的(如果不懂的话自行搜索下基数排序)
    sa[1][sum[r[t][sa[0][j]]]--]=sa[0][j];
}
int main()
{
    scanf("%s",ch+1);
    n=strlen(ch+1);
    rep(i,n)
    sum[ch[i]]=1;
    for(int i=1;i<S;++i)
    sum[i]+=sum[i-1];
    rep(i,n)
    r[0][i]=sum[ch[i]];//函数外的sum用于求出初始排名
    for(int i=1;i<n;i<<=1)
    {
        getsa(i);
        t^=1;
        rep(j,n)
        r[t][sa[1][j]]=r[t^1][sa[1][j]]==r[t^1][sa[1][j-1]]&&
                    r[t^1][sa[1][j]+i]==r[t^1][sa[1][j-1]+i]?
                    r[t][sa[1][j-1]]:r[t][sa[1][j-1]]+1;
        if(r[t][sa[1][n]]==n)break;//已经排好序了便可以提前退出
    }
    printf("RANK: ");
    rep(i,n)
    printf("%d ",r[t][i]);
    printf("\nSA:   ");
    rep(i,n)
    printf("%d ",sa[1][i]);
    return 0;
}
时间: 2024-10-08 13:01:46

用倍增法构造后缀数组中的SA及RANK数组的相关文章

后缀数组(Suffix Array)模板及简析——Part 1:构建SA和rank数组

后缀数组(Suffix Array,SA)是处理字符串的有力工具.它比后缀树更易实现,占用空间更少,并且同样可以解决千变万化的字符串问题 首先推荐罗穗骞的论文(网上搜一下就能搜到),里面对后缀数组的定义.实现和应用都做了详细的阐述 然而不幸的是罗神犇的代码简直魔性,蒟蒻表示这代码压的根本看不懂啊…… 所以在理解了后缀数组的构建过程之后,我重新写了一份模板代码.虽然啰嗦了点(代码比较大,而且变量名故意拉长了),不过相对比较好懂 而且论文中用到的辅助空间是4N,我的模板用了3N,事实上还可以优化到只

数组中 最大和 的子数组

题目: 输入一个整型数组,数据元素有正数也有负数,求元素组合成连续子数组之和最大的子数组,要求时间复杂度为O(n). 例如: 输入的数组为1, -2, 3, 10, -4, 7, 2, -5,最大和的连续子数组为3, 10, -4, 7, 2,其最大和为18. 背景: 本题最初为2005年浙江大学计算机系考研题的最后一道程序设计题,在2006年里包括google在内的很多知名公司都把本题当作面试题. 由于本题在网络中广为流传,本题也顺利成为2006年程序员面试题中经典中的经典. 分析: 如果不考

0131 JavaScript数组中新增元素:修改数组索引、修改 length 长度、数组翻转

? 数组中可以通过以下方式在数组的末尾插入新元素: 数组[ 数组.length ] = 新数据; 1.5.1 通过修改 length 长度新增数组元素 可以通过修改 length 长度来实现数组扩容的目的 length 属性是可读写的 var arr = ['red', 'green', 'blue', 'pink']; arr.length = 7; console.log(arr); console.log(arr[4]); // undefined console.log(arr[5]);

将一个任意整数插入到已排列的整型数组中,插入后,数组中的数仍保持有序

实现代码: package homework; import java.util.Scanner; public class HomeWork { public static void main(String[] args) { //將输入的数字插入到一个有序的数组中的合适位置 Scanner input = new Scanner(System.in); System.out.println("请输入一个数字:"); int num = input.nextInt(); //有序的整

百度:在O(1)空间复杂度范围内对一个数组中前后连段有序数组进行归并排序

一.题目理解 题目:数组al[0,mid-1]和al[mid,num-1]是各自有序的,对数组al[0,num-1]的两个子有序段进行merge,得到al[0,num-1]整体有序.要求空间复杂度为O(1).注:al[i]元素是支持'<'运算符的. 数据结构第一章就讲了有序表合并,不过那时候是合并到新表,判断条件是while(i<len1||j<len2),然后把a1或者a2数组(只有一个,因为另一个必定已经完全插入进了c数组,这也是为什么while条件是“或”)后面的元素:如果数据结构

返回数组中的最大值和删除数组重复值-排序

//数组中最大值function getMax(arr){ //取该数组第一个值为最大值 var max=arr[0]; for(var i=0;i<arr.length;i++){ if(arr[i]>max){ max=arr[i] } } return max;} console.log(getMax([2,98,10,88])) /*--------------------------------------------------------------------*///删除数组重

LeetCode448-找到所有数组中消失的数字(原地数组)

因为题目数组有范围1 ≤ a[i] ≤ n 找到所有在 [1, n] 范围之间没有出现在数组中的数字 就是说,这个数组,如果数字是全的话,应该可以表示为 [1,2,3,4,5,6..........,N] 有重复的数字,就是说一些位置上的数字被其他数字占了,那么把它回复原样,在看一下那些位置上的数字不对就行了. 一个数字应该在的index,是数字-1,比如1放在index0那. 一开始的想法是,从index0开始遍历,遇到和index不同的,就放到他应该在的位置 . 然后取出那个位置上的数字,继

JS从数组中随机取出几个数组元素的方法

原文链接:http://caibaojian.com/js-get-random-elements-from-array.html js如何从一个数组中随机取出一个元素或者几个元素. 假如数组为· var items = ['1','2','4','5','6','7','8','9','10']; 1.从数组items中随机取出一个元素 var item = items[Math.floor(Math.random()*items.length)]; 2.从前面的一篇随机数组中随机取几个元素

3.键盘输入10个数,放到数组中,(1)去除该数组中大于10的数 (2)将该数组中的数字写入到本地文件number.txt中

package cn.it.text; import java.io.FileWriter; import java.io.IOException; import java.util.Scanner; /* * 3.键盘输入10个数,放到数组中 (1)去除该数组中大于10的数 (2)将该数组中的数字写入到本地文件number.txt中 */ public class Test3 { public static int[] arr = new int[10]; public static void