学习笔记----后缀数组

学习资料:IOI2009国家集训队论文——《后缀数组》

论文里面写的比较清晰了,但是代码里面没有解释,又从网上找到了一份代码的注释,解释的挺好的

地址:http://www.cnblogs.com/Lyush/p/3233573.html

这里是代码模板:

倍增算法实现的,效率很高。

const int maxn = 10010;
int wa[maxn], wb[maxn], wv[maxn], ws1[maxn];
int cmp(int *r, int a, int b, int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int *sa,int n,int m)
{
    int i, j, p, *x = wa,*y = wb;
    // 下面四行是对第一个字母的一个基数排序:基数排序其实就是记录前面有多少个位置被占据了
    for(i = 0; i<m; i++) ws1[i]=0; // 将统计字符数量的数组清空
    for(i = 0; i<n; i++) ws1[x[i]=r[i]]++; // 统计各种字符的个数
    for(i = 1; i<m; i++) ws1[i]+=ws1[i-1]; // 进行一个累加,因为前面的小字符集对后面字符的排位有位置贡献
    for(i = n-1; i>=0; i--) sa[--ws1[x[i]]]=i; // 根据位置来排序,sa[x] = i,表示i位置排在第x位
    // wa[x[i]]就是字符集0-x[i]共有多少字符占据了位置,减去自己的一个位置剩下的就是自己的排名了,排名从0开始
    // 排名过程中主要的过程是对于处于相同字符的字符的排序,因为改变wa[x[i]]值得只会是本身,小于该字符的贡献值
    // 是不变的,对于第一个字符相同的依据是位置关系,在后面将看到通过第二个关键字来确定相同字符的先后关系

    // 这以后的排序都是通过两个关键字来确定一个串的位置,也即倍增思想
    // 通过将一个串分解成两部分,而这两部分的位置关系我们都已经计算出来
    for(j = 1, p = 1; p<n; j*=2, m=p)
    {
        for(p = 0, i = n-j; i<n; i++) y[p++]=i; // 枚举的串是用于与i位置的串进行合并,由于i较大,因为匹配的串为空串
        // 由于枚举的是长度为j的串,那么i位置开始的串将凑不出这个长度的串,因此第二关键字应该最小,这其中位置靠前的较小
        for(i = 0; i<n; i++)
            if(sa[i]>=j) y[p++]=sa[i]-j; // sa[i]-j开头的串作为第二关键字与编号为sa[i]的串匹配,sa[i]<j的串不用作为第二关键字来匹配
        for(i = 0; i<n; i++) wv[i]=x[y[i]]; // 取出这些位置的第一关键字
        for(i = 0; i<m; i++) ws1[i]=0;
        for(i = 0; i<n; i++) ws1[wv[i]]++;
        for(i = 1; i<m; i++) ws1[i]+=ws1[i-1];
        for(i = n-1; i>=0; i--) sa[--ws1[wv[i]]]=y[i]; // 按照第二关键字进行第一关键字的基数排序
        for(swap(x,y),p=1,x[sa[0]]=0,i=1; i<n; i++) // 对排好序的sa数组进行一次字符集缩小、常数优化
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    return;
}

int rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n) // 这里的n是原串的本来长度,即不包括新增的0
{
    int i,j,k=0;
    for(i = 1; i<=n; i++) rank[sa[i]]=i; // 有后缀数组得到名次数组,排名第0的后缀一定是添加的0
    for(i = 0; i<n; height[rank[i++]]=k) // 以 i 开始的后缀总能够从以 i-1 开始的后缀中继承 k-1 匹配项出来
        for(k?k--:0, j=sa[rank[i]-1]; r[i+k] == r[j+k]; k++); // 进行一个暴力的匹配,但是整个算法的时间复杂度还是O(n)的
    return;
}

int main()
{

    return 0;
}

模板:

const int maxn = 10010;
int wa[maxn], wb[maxn], wv[maxn], ws1[maxn];
int cmp(int *r, int a, int b, int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int *sa,int n,int m)
{
    int i, j, p, *x = wa,*y = wb;

    for(i = 0; i<m; i++) ws1[i]=0;
    for(i = 0; i<n; i++) ws1[x[i]=r[i]]++;
    for(i = 1; i<m; i++) ws1[i]+=ws1[i-1];
    for(i = n-1; i>=0; i--) sa[--ws1[x[i]]]=i;

    for(j = 1, p = 1; p<n; j*=2, m=p)
    {
        for(p = 0, i = n-j; i<n; i++) y[p++]=i; 

        for(i = 0; i<n; i++)
            if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i = 0; i<n; i++) wv[i]=x[y[i]];
        for(i = 0; i<m; i++) ws1[i]=0;
        for(i = 0; i<n; i++) ws1[wv[i]]++;
        for(i = 1; i<m; i++) ws1[i]+=ws1[i-1];
        for(i = n-1; i>=0; i--) sa[--ws1[wv[i]]]=y[i];
        for(swap(x,y),p=1,x[sa[0]]=0,i=1; i<n; i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    return;
}

int rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n)
{
    int i,j,k=0;
    for(i = 1; i<=n; i++) rank[sa[i]]=i;
    for(i = 0; i<n; height[rank[i++]]=k)
        for(k?k--:0, j=sa[rank[i]-1]; r[i+k] == r[j+k]; k++);
    return;
}

int main()
{

    return 0;
}
时间: 2024-10-10 05:22:20

学习笔记----后缀数组的相关文章

C和指针 学习笔记-3.数组与指针

数据名代表首地址 指向数组的指针 #include <stdio.h> extern void iterate(int *p); void main(){ int a[]={1,2,3,4,5}; iterate(a); } void iterate(int *p){ int i; for(i=0;i<5;i++){ printf("%d",*p++); } } 指向一维数组的指针 int (*p)[3] #include <stdio.h> void i

Swift学习笔记(12)--数组和字典的复制

Swift中,数组Array和字典Dictionary是用结构来实现的,但是数组与字典和其它结构在进行赋值或者作为参数传递给函数的时候有一些不同. 并且数组和字典的这些操作,又与Foundation中的NSArray和NSDictionary不同,它们是用类来实现的. 注意:下面的小节将会介绍数组,字典,字符串等的复制操作.这些复制操作看起来都已经发生,但是Swift只会在确实需要复制的时候才会完整复制,从而达到最优的性能. 字典的赋值和复制操作 每次将一个字典Dictionary类型赋值给一个

Swift学习笔记(5)--数组

数组的下标从0开始计数,相关方法属性涉及到下标时也从0开始计数 1.定义: //1.可变数组 var cityArray = ["Portland","San Francisco","Cupertino"] //2.不可变数组 let cityArray2 = ["Portland","San Francisco","Beijing"] //3.空数组 var animalArray =

JavaScript学习笔记:数组reduce()和reduceRight()方法

很多时候需要累加数组项的得到一个值(比如说求和).如果你碰到一个类似的问题,你想到的方法是什么呢?会不会和我一样,想到的就是使用for或while循环,对数组进行迭代,依次将他们的值加起来.比如: var arr = [1,2,3,4,5,6]; Array.prototype.sum = function (){ var sumResult = 0; for (var i = 0; i < this.length; i++) { sumResult += parseInt(this[i]);

【算法学习】后缀数组

一个字符串的题,有姿势水平的OIers的脑中应该要浮现出许多算法-- 但是我没有姿势,也没有水平,除了KMP和trie树,什么也想不起来. 直到我学了它--后缀数组! 多亏这玩意儿,我现在什么都想不起来了. 后缀数组干嘛用的? 主要处理同一个字符串中的重复子串问题. 如何实现? 注意到每一个子串,都是一个后缀的某个前缀,这个后缀和前缀都是唯一确定的. 而后缀相同的前缀,和他们的字典序有密切联系.你有没有想过,字典中的相邻单词,他们的公共前缀总是很长. 一个字符串的任意后缀,都能用它的起始位置的下

算法学习:后缀数组 height的求取

[定义] [LCP]全名最长公共前缀,两个后缀之间的最长前缀,以下我们定义 lcp ( i , j ) 的意义是后缀 i 和 j 的最长前缀 [z函数] 函数z [ i ] 表示的是,第 i 个后缀和字符串的最长前缀  [解决问题] 这两个算法都是在解决这个问题 即求后缀和字符串和后缀之间的最长公共前缀 但是有所不同的是, 后缀数组最终求出的是,字典序第 i 个后缀和第 i + 1 个后缀的最长公共前缀 z函数最终求出的是,第 i 个后缀和字符串的最长公共前缀 然后通过这个最长公共前缀求一些其他

es6学习笔记(2)数组(中)

接着上一篇,给大家再分享一些数组的其他方法.大家也可以去点击这里学习数组更多的方法 concat方法: 概述:    concat() 方法将传入的数组或非数组值与原数组合并,组成一个新的数组并返回. 参数: valueN:需要与原数组合并的数组或非数组值. 描述: concat 方法将创建一个新的数组,然后将调用它的对象(this 指向的对象)中的元素以及所有参数中的数组类型的参数中的元素以及非数组类型的参数本身按照顺序放入这个新数组,并返回该数组. concat方法并不修改调用它的对象(th

JavaScript新手学习笔记1——数组

今天,我复习了一下JavaScript的数组相关的知识,总结一下数组的API: 总共有11个API:按照学习的先后顺序来吧,分别是: ① toString()  语法:arr.toString(); 将数组转化为字符串,并且返回这个字符串,以逗号分隔:但是不改变原数组: 1 var arr=[1,2,3,4,5]; 2 var result=arr.toString(); 3 console.log(result); //1,2,3,4,5 4 console.log(arr); //[1,2,

数据结构学习笔记&mdash;&mdash;顺序数组1

线性表最简单的刚开始就是顺序存储结构,我是看着郝斌的视频一点一点来的,严蔚敏的书只有算法,没有具体实现,此笔记是具体的实现 为什么数据结构有ADT呢,就是为了满足数据结构的泛性,可以在多种数据类型使用 这里所说的数组并不是简单那种数组,这里所讲的是数组结构,就是在内存中是连续存储的,所以要先构造出一个这样的结构 typedef int ElemType; typedef struct { ElemType* elem; //数组结构中的数据区,可以是任何数据类型,常见的是结构体类型 int le