后缀数组学习笔记【详解|图】

后缀数组学习笔记【详解】

老天,一个后缀数组不知道看了多少天,最后终于还是看懂了啊!

最关键的就是一会儿下标表示排名,一会用数值表示排名绕死人了。

我不知道手跑了多少次才明白过来。其实我也建议初学者手跑几遍,但是一定要注意数组的意义,否则就是无用功。

数组含义:

s[ ]:输入的字符串,预处理的时候会在末尾加上一个0

sa[ ]:它的下标就是后缀排名

x[ ] = t[ ]:用来保存第一关键字排名,注意!它的数值是排名。初始时恰好是字符串的ASCII码。字典序嘛!

y[ ] = t2[ ]:它的下标就是第二关键字排名,第二关键字是直接从sa[ ]当中提取的,关系极其密切

c[ ]:用来基数排序。初始值恰好是每种字符出现的次数。后来它的作用就跟基数排序密切相关,建议学习基数排序

有一点一定要注意!第二关键字来自sa[ ]数组,但是第一关键字并不是来自sa[ ]数组!这一点不知道迷惑了多少人,就是因为论文里给出的图完全就是原理图,不是代码实现的图,不搭噶的!

P.S. 为了优化时间空间,避免新开一个中间数组来复制t[ ]的值,采用了将它的指针x和t2[ ]的指针y交换的方法。注意这个时候t2[ ]已经没有用了。

我先给出一个足以理解后缀数组的增加了中间输出的代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000, M = 130;
char s[N];
int sa[N], t[N], t2[N], c[M], n;
int rank[N], high[N];

#define DBG
#ifdef DBG
int db[N];
void debug(int *f)
{
    for(int i = 0; i < n; i++) {
        db[f[i]] = i;
    }
    printf("%3d", db[0]);
    for(int i = 1; i < n; i++) {
        printf(" %3d", db[i]);
    }puts("]");
}
#endif

bool cmp(int *y, int i, int k)
{
    return y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+k] == y[sa[i]+k];
}

void build(int m)
{
    int i, *x = t, *y = t2;
    for(i = 0; i < m; i++) c[i] = 0;
    for(i = 0; i < n; i++) c[x[i] = s[i]]++;
    for(i = 1; i < m; i++) c[i] += c[i-1];
    for(i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;

#ifdef DBG
    printf("sa Get:[");
    debug(sa);
    puts("");
#endif

    for(int k = 1, p; k <= n; k<<=1, m=p) {
        p = 0;
        //y[]的下标就是对应的第二关键字排名,它是由sa[]直接得来的
        //另外y[]的内容就是第一关键字所在位置
        for(i = n-k; i < n; i++) y[p++] = i;
        for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;

#ifdef DBG
        printf("Gain y:[");
        debug(y);
        printf("Look x:{");
        printf("%3d", x[0]);
        for(i = 1; i < n; i++) {
            printf(" %3d", x[i]);
        }puts("}");
#endif

        //x[]的内容就是对应的第一关键字排名
        //根据x[]的内容和y[]的下标进行合并,得到新的排名作为sa[]的下标
        for(i = 0; i < m; i++) c[i] = 0;
        for(i = 0; i < n; i++) c[x[y[i]]]++;
        for(i = 1; i < m; i++) c[i] += c[i-1];
        for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];

#ifdef DBG
        printf("sa Get:[");
        debug(sa);
        puts("");
#endif

        //按照sa[]的顺序提取出老的x[],计算新的x[]
        swap(x, y);
        p = 1; x[sa[0]] = 0;//sa[0]一定是添加的字符0,排名万年第0
        for(i = 1; i < n; i++) {
            x[sa[i]] = cmp(y, i, k) ? p-1 : p++;
        }
        //剪枝,此时x[]中已经没有相同的值,sa[]被确定
        if(p >= n) break;
    }
}

void get_high()
{
    int k = 0;
    for(int i = 0; i < n; i++) rank[sa[i]] = i;
    for(int i = 0; i < n; i++) {
        if(k) k--;
        int j = sa[rank[i]-1];
        while(s[i+k] == s[j+k]) k++;
        high[rank[i]] = k;
    }
}

void PR()
{
    printf("The rank is:\n");
    printf("%d", rank[0]);
    for(int i = 1; i < n-1; i++) printf(" %d", rank[i]);
    puts("");
}

int main()
{
	scanf("%s", s);
	n = strlen(s) + 1;
	int maxi = 0;
	for(int i = 0; i < n; i++) {
		maxi = maxi > s[i] ? maxi : s[i];
	}
	s[n-1] = 0;
    build(maxi+1);
    get_high();
#ifdef DBG
    PR();
#endif
   	return 0;
}

根据这份代码,输入一些数据测试一下,仔细研究研究中间输出。

建议数据:

abaab

aabaaaab

banana

接下来是手跑过程:

方框代表里面的值是下标,花括号代表是数值。它们都是和第一行红色数字一一对应的。

我们暂时不去管第一关键字是怎样计算出来的。

根据上面的程序,自己来填写这张图当中的数值。一个一个填写就可以明白了。(x[ ]数组的值就直接看图上的,并且注意每一个x[ ]数组都是在上一层基数排序计算出来的)

sa[ ]的初始值恰好是根据字符出现次数一个一个来的,轻易就可以手跑出来。这就完成了一位数的基数排序。

蓝色的字是第二关键字,正好是从sa[ ]当中提取出来的。黄色的箭头表示没有第二关键字,它们的排名是自左向右从0开始填的,要先填完这个再提取其他的第二关键字。再次强调,虽然有线,但是第一关键字并不是sa[ ]数组当中的数!

然后给出的x[ ]和刚填完的y[ ]合并(绿色字体),计算出sa[ ]。这是两位数的基数排序。

接下来继续倍增,完成四位数的基数排序。(如果你困惑为什么还是只有两个数被线指着,建议阅读论文)

最后,其实本来是不用对八位数进行基数排序,因为这个时候新的x[ ]数组(图中倒数第二行)里面已经没有重复的排名了,而第一关键字是首要的,因此sa[ ]数组被确定下来了。这里可以加个剪枝,break一下。

怎样得到x[ ]数组:

在每一次得到sa[ ]数组之后,计算新的x[ ],方法是按照sa[ ]当中的排名顺序,(即sa[1...n])提取出旧的x[ ](注意此时它的名字叫做y[ ]了)来计算。如果某字串跟之前的那个完全一样(即cmp()函数),排名就一样(p-1)。

根据上面的话,再来自己填写x[ ]数组吧!

时间: 2024-10-25 04:25:31

后缀数组学习笔记【详解|图】的相关文章

后缀数组(suffix array)详解

后缀数组(suffix array)详解 转载请注明:http://www.cnblogs.com/acmer-jsb/p/3988683.html 一.What  Is  Suffix Array? 用我的理解,后缀数组是一种功能强大的字符串处理工具,堪称字符串处理神奇,尤其是在字符串匹配方面更是有着出色的处理能力. 其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多.可以说,在信息学竞赛中后缀

JMeter学习笔记--详解JMeter逻辑控制器

JMeter使用逻辑控制器来决定采样器的处理顺序 简单控制器(Simple Controller):存储设备(将某些采样器归组) 循环控制器(Loop Controller:设置循环次数 仅一次控制器(Once Only Controller) 交替控制器(Interleave Controller) 随机控制器(Random Controller) 随机顺序控制器(Random Order Controller): 每个子测试元件都至多执行一次,但是执行顺序是随机的 吞吐量控制器(Throug

后缀数组学习笔记

现在来看倍增算法是非常好理解的. 直接放一篇blog写的挺好的:http://www.cnblogs.com/zinthos/p/3899725.html 虽然理论复杂度是$O(nlogn)$,但其中各种细节优化确实十分有必要的. 给自己放一个倍增的模板,有空填DC3的坑 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 int n,m; 6 cha

JMeter学习笔记--详解JMeter配置元件

JMeter配置元件可以用来初始化默认值和变量,以便后续采样器使用.将在其作用域的初始化阶段处理. CSV Data Set Config:被用来从文件中读取数据,并将它们拆分后存储到变量中,适合处理众多变量 Variable Names:变量名列表(逗号分隔).JMeter2.3.4以后的版本,支持CSV标题行,如果变量名为空,那么文件的第一行将被读取,并被解释为列名的列表.这些变量名必须使用分割符加以区分,他们可以使用双引号加以引用.默认情况下,该文件仅打开一次,而每个线程会使用文件中不同的

C#学习笔记--详解委托,事件与回调函数

.Net编程中最经常用的元素,事件必然是其中之一.无论在ASP.NET还是WINFrom开发中,窗体加载(Load),绘制(Paint),初始化(Init)等等.“protected void Page_Load(object sender, EventArgs e)”这段代码相信没有人不熟悉的.细心一点一定会发现,非常多的事件方法都是带了“object sender, EventArgs e”这两个参数.这是不是和委托非常相似呢? 一.委托(有些书中也称为委派) 委托是什么呢?这个名字的意思已

JMeter学习笔记--详解JMeter定时器

定时器的处理优先于同一作用域内的采样器,如果在同一作用域内有多个定时器,那么所有的定时器都会在每个采样器之前处理. 若定时器所在作用域内无采样器,那么定时器不会被处理 固定定时器,每个线程在请求之间间隔固定时长 Gaussian Random Timer: 会暂停每个线程请求一个随机时长,而大多数时间间隔接近于一个固定值 Uniform Random Timer:会暂停每个线程请求一个随机时长,每个时间间隔都有同样的出现几率 Constant Throughput Timer:可变暂停时长,通过

005-Scala数组操作实战详解

005-Scala数组操作实战详解 Worksheet的使用 交互式命令执行平台 记得每次要保存才会出相应的结果 数组的基本操作 数组的下标是从0开始和Tuple不同 缓冲数组ArrayBuffer(长度可变) 数组的进阶操作 多维数组 常用数组使用方法分析 1.可通过yield产生新的数组并赋值给变量 2.for循环中也同时可以添加if过滤器来过滤数据再产生新的数据 3.c.filter(_%2==0).Map(2*_)生产环境经常会使用的表达方法(重点) 4.数组和缓冲数组都是可以直接调用其

php中二维数组排序问题方法详解

合肥开源IT教育分享一篇<php中二维数组排序问题方法详解>的文章希望能够帮助在学习php的新手们,如果还有什么不懂的问题 可以关注我们的官方网站:www.kyitjy.com  豪华的名师团队,多位技术专家授课,多位核心研发工程师研发授课. PHP中二维数组排序,可以使用PHP内置函数uasort() 示例一: 使用用户自定义的比较函数对数组中的值进行排序并保持索引关联 回调函数如下:注意回调函数的返回值是负数或者是false的时候,表示回调函数的第一个参数在前,第二个参数在后排列 $per

WebGL/Three.js深度学习课程详解

课程介绍:适用于对WebGL.Three.js等3D技术感兴趣,却不知道如何入门的同学, 课程带领大家深入理解WebGL的原理. 课程目录:├─01-基础部分│      01-WebGL与three.js的基础.与opengl的关系.mp4│      02-编写第一个three.js程序.mp4│      03-three.js程序框架,绘制一条直线.mp4│      04-三维世界的组成(点.线).mp4│      05-坐标系的秘密(世界坐标.本地坐标).mp4│      06-