后缀数组模板 (详细注释)
2015年5月初次学习后缀数组, 当时是 error202 讲的. 代码一直不熟练, 导致运用得不熟练, 2016年 ACM-ICPC China Finals 就有一道后缀数组的裸题, 我们队并没有过. 现在回想起来, 真是荒废了大把时间, 悔之晚矣.
Implementation
后缀数组原理比较好理解, 实现起来也有比较简单的写法. 下面的实现参考了 2009 年国家集训队队员罗穗骞的论文《后缀数组--处理字符串的有力工具》中的实现. 论文中给出的实现比较巧妙, 但比较难理解, 需要读者懂得计数排序(counting sort)和基数排序(radix sort)作为预备.
//比较两个串是否相同
//分别比较两个关键字
bool same(int *rank, int idx1, int idx2, int len){return rank[idx1]==rank[idx2] && rank[idx1+len]==rank[idx2+len];
}// 输入字符串的末尾要补一个 '0' (C-style string 默认如此), n是字符串的实际长度+1.
void SA(int *s, int *sa, int *sa2, int *rk, int *cnt, int *hgt, int n, int m){//counting sortfor(int i=0; i<m; i++) cnt[i]=0;for(int i=0; i<n; i++) cnt[rk[i]=s[i]]++;for(int i=1; i<m; i++) cnt[i]+=cnt[i-1];for(int i=n-1; i>=0; i--) sa[--cnt[rk[i]]]=i; //stable sortfor(int len=1; len<n; len*=2){//step-1: 计算(填充) sa2[]//sa2[]: 按第二关键字对后缀排序的结果//对后缀按第二关键字排序这一步可直接用上一次的sa[]数组, 这个过程可形象地理解为将后缀按顺序从一个数组中拿出来放到另一个数组中int p=0;for(int i=n-len; i<n; i++) sa2[p++]=i; //第二关键字为空的后缀放在最开头//接着放第二关键字非空的//形象化地理解: 将后缀从一个数组里按顺序拿出来放到另一个数组里for(int i=0; i<n; i++) if(sa[i]>=len) sa2[p++]=sa[i]-len;//step-2 fill sa[], countig sort//计数排序: 每个后缀的第一关键字 (first key) 为上一次针对长度减半的 (部分) 后缀求出来的rankfor(int i=0; i<m; i++) cnt[i]=0;for(int i=0; i<n; i++) cnt[rk[i]]++;for(int i=1; i<m; i++) cnt[i]+=cnt[i-1];for(int i=n-1; i>=0; i--) sa[--cnt[rk[sa2[i]]]]=sa2[i];//step-1 and step-2 together constitute a radix sort//fill rk[]swap(rk, sa2);rk[sa[0]]=0;for(int i=1; i<n; i++)//这里, sa2指向的是旧rank数组rk[sa[i]]=rk[sa[i-1]]+!same(sa2, sa[i-1], sa[i], len);m=rk[sa[n-1]]+1;if(m==n) break;}//CALCULATE hgt[]for(int i=0, j, lcp=0; i<n-1; i++){lcp?--lcp:0;// rk[i]>0j=sa[rk[i]-1]; for(; s[j+lcp]==s[i+lcp]; lcp++);hgt[rk[i]]=lcp;}
}
实现要点
void SA(int *s, int *sa, int *sa2, int *rk, int *cnt, int *hgt, int n, int m)
s
: 输入字符串, 末尾补一个特殊字符 \0
.
sa
: 后缀数组
sa2
: 以第二关键字对后缀排序所得数组, 辅助数组
cnt
: 用于计数排序的辅助数组
rk
: rank数组, 辅助数组
n
: 输入字符串 s 的长度, 为其实际长度+1
m
: 单字符rank的范围, 辅助变量.
在输入字符串 s
末尾补 0
的好处
罗穗骞论文中概括为
为了函数操作的方便
具体为
bool same(int *rank, int idx1, int idx2, int len){return rank[idx1]==rank[idx2] && rank[idx1+len]==rank[idx2+len]; }
从
same()
的实现上, 可以看出规定s[n-1]=0
的好处, 如果s[idx1]==s[idx2]
(注意idx1 != idx2
), 说明以idx1
或idx2
开头的长度为len
的字符串肯定不包括字符s[n-1]
, 所以调用变量rank[idx1+len]
和rank[idx2+len]
不会导致数组越界, 这样就不需要做特殊判断.
另外,在求 hgt[]
时,
for(int i=0, j, lcp=0; i<n-1; i++){lcp?--lcp:0;// rk[i]>0j=sa[rk[i]-1]; for(; s[j+lcp]==s[i+lcp]; lcp++);hgt[rk[i]]=lcp;}
由于输入数组末尾补零的缘故, 有 $\mathrm{rk}[n-1]=0$ , $\mathrm{rk}[i]>0, (0\le i < n-1)$ .
从而 j=sa[rk[i]-1];
不会越界.
hight 数组的求法
height[i]: sa[i] 和 sa[i-1] 两后缀的最长公共前缀 (LCP) 的长度.
height[i] 满足如下性质:
$$\mathrm{height}[\mathrm{rank}[i]] \ge \mathrm{height}[\mathrm{rank}[i-1]]-1$$
依据这个性质, 我们可以按 $\mathrm{height}[\mathrm{rank}[0]], \mathrm{height}[\mathrm{rank}[1]], \dots, \mathrm{height}[\mathrm{rank}[n-1]]$ 的顺序计算 $\mathrm{height}[]$ 数组, 复杂度为 $O(n)$ .
注意: 上面的实现是在 SA()
内部计算的 hgt[]
的. 计算 hgt[]
需借助 rank[]
, 若要在 SA()
外面计算 hgt[]
, 则须根据求出的 sa[]
再算一遍 rk[]
. rk
是作为指针传入 SA()
的, 然而 SA()
中有 swap(rk, sa2);
这一条语句, 因此在 SA()
外部并不能断定 rk[]
里存的是什么.
转载于:https://www.cnblogs.com/Patt/p/6270404.html
后缀数组模板 (详细注释)相关推荐
- 【BZOJ 1031】[JSOI2007]字符加密Cipher(后缀数组模板)
[题目链接]:http://www.lydsy.com/JudgeOnline/problem.php?id=1031 [题意] [题解] 后缀数组模板题; 把整个字符串扩大一倍. 即长度乘2 然后搞 ...
- 后缀数组 java实现_后缀数组模板 - java开发指南博客 【转载】 - ITeye博客
//后缀数组模板 int wa[maxn],wb[maxn],wv[maxn],ws[maxn];//这些都是需要用到的中间变量 int cmp(int *r,int a,int b,int l) { ...
- 后缀数组模板及代码详解
后缀数组代码详解 上图中存在直边和斜边,下文会用到. #include <cstdio> #include <cstring> #include <iostream> ...
- 洛谷P3809 后缀数组模板
题目:https://www.luogu.org/problemnew/show/P3809 刚学了后缀数组,看人家手写演示了半天,大概明白了过程,但完全写不出来代码: 于是借鉴了许多,不过都差不多, ...
- 后缀数组模板 hdu1403
题意:就是让你求两个字符串的最大子串 #include <bits/stdc++.h> const int maxn=200005; using namespace std; int s[ ...
- 刘汝佳蓝书后缀数组模板解释及补全
相信很多初学后缀数组的ACMer在学习蓝书中的后缀数组部分遇到了一些障碍,可能像我一样看明白了P219 --220的讲解和算法,百度了基数排序的方法,然后被卡在P221的代码上了,本文目的即分享我对这 ...
- UOJ #35. 后缀排序 后缀数组 模板
http://uoj.ac/problem/35 模板题,重新理了一遍关系.看注释吧.充分理解了倍增的意义,翻倍之后对上一次排序的利用是通过一种类似于队列的方式完成的. 1 #include<i ...
- 后缀数组(倍增)学习记录,我尽可能详细的讲了
后缀数组(倍增) 后缀数组 后缀数组能干什么 一些基本概念 那么到底怎么排序呢? 倍增排序 具体执行排序呢? 基数排序 关于排序的桶 关于桶排序在字符串倍增中的嵌入 具体改执行的排序事情 倍增排序的代 ...
- bzoj 1031 [JSOI2007]字符加密Cipher 后缀数组
题面 题目传送门 解法 后缀数组模板题吧-- 将字符串两倍,然后求一遍sa数组即可 时间复杂度:\(O(n\ log\ n)\) 代码 #include <bits/stdc++.h> # ...
- [学习笔记]后缀数组
参考: 后缀数组 最详细讲解 上面一篇是转载这一篇的: 后缀数组 学习笔记 一. 后缀:suff(i),后缀要排序 sa[i],排名为i的后缀开始位置 rk[i],i开始位置的后缀的排名. rk[sa ...
最新文章
- AQS独占式同步队列入队与出队
- CSS3实现页面的平滑过渡
- idea 项目编译不成功-循环依赖的问题
- Centos下安装配置WordPress与nginx教程
- 数字图像处理——引导滤波
- 数据库开发基本操作-配置SQL Server 2005 Express的身份验证方式,以及如何启用sa登录名...
- 简单自制拖拽布局思路vue-Cil
- 菜鸟认知--DIP,Ioc,DI,Ioc容器
- Linux 内核/sys 文件系统之sysfs 属性文件
- open函数_全!Python函数和文件操作合集(长文系列第三篇)
- 模拟退火算法_Simulated Annealing 模拟退火算法
- Jquery-无法有效获取当前窗口高度
- 市场调研策划书_市场调研计划书模板
- python图片ocr识别手写印刷体中英文字体
- android 分割数字图片,Android开发自定义View实现数字与图片无缝切换的2048
- java 通过SSL/TLS加密https建立连接
- html代码可以在dw用吗,HTML基础DW使用教程(示例代码)
- API文档自动生成的方法
- GetLastError 返回值意义
- 虚拟机快照如何强行删除,虚拟机如何正常启动, 虚拟机.vmem文件的删除