后缀数组学习笔记

这篇具有很好参考价值的文章主要介绍了后缀数组学习笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

后缀数组是什么

后缀数组就是主要处理字符串后缀问题的,它的实现算法主要有两种:倍增法和 DC3,复杂度分别是 \(O(n\log n)\)\(O(n)\)。这里由于 DC3 代码答辩且难以理解,我就只写了倍增法的实现。

例题引入

P3809 【模板】后缀排序

题目大意

读入一个长度为 \(n\) 的由大小写英文字母或数字组成的字符串,把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 \(1\)\(n\)

前置芝士

  • 基数排序

算法

后缀数组记录的就是字符串第 \(i\) 个后缀的排名。
比如一个字符串 \(S=abaababa\) 的后缀就是:
后缀数组学习笔记
如果我们暴力去提取出 \(S\) 的所有后缀在给它排序显然是不行的,因为它的时间复杂度是 \(O(n^2\log n)\) 的。

倍增法

既然单纯的暴力不行,我们就需要考虑用倍增来处理。
倍增法的主要思想是:
每次将所有后缀按照前 \(2^k\) 个字符排序,最后得出所有后缀的排序。
这样问题就转化为了:现在已知所有后缀关于前 \(2^{k−1}\) 个字符的排序,要对所有后缀排序按前 \(2^k\) 个字符排序。
那么我们就可以将前 \(2^k\) 个字符分为两部分,每一部分的长度都是 \(2^{k−1}\),并且这两部分按照字典序排序后的名次是已知的。
这里我们定义前一部分的排名为第一关键字 \(key1\),后一部分的排名为第二关键字 \(key2\)。然后每次按照关键字进行排序,更新 \(rank\) 数组,得到新的排名。
这就是倍增法对后缀数组的处理,比如当处理 \(S=aabaaaab\) 时过程如图:
后缀数组学习笔记
注意,这里处理第一第二关键字排序时,需要先按第一关键字的大小排序,再用第二关键字来排,其思想与基数排序很像。
由于字符串的每个后缀都是不同的(至少长度不同),所以最后得到的 \(rank\) 数组里的数必定是互不相同的。同样,如果再做第 \(k\) 次倍增时,得到的 \(rank\) 数组如果已经互不相同了,那么就说明已经找到了最终的 \(rank\) 数组,可以直接退出循环了。

实现方法

要想通过倍增法求出后缀数组有两种排序方法。

快速排序 \(O(n\log ^2n)\)

用快速排序来做倍增法是很直观,有助于初学者更好地理解后缀数组-倍增法的思路,代码也很简单。

Code

#include<bits/stdc++.h>
#define int long long
#define N 1000005
#define Mod 1000000007
#define For(i,j,k) for(long long i=j;i<=k;++i)
#define FoR(i,j,k) for(long long i=j;i<k;++i)
#define FOR(i,j,k) for(long long i=j;i>=k;--i)
using namespace std;
struct Node{
	int key1,key2,i;
}d[N];
int n,rk[N],sa[N];
string s;
inline bool cmp(Node a,Node b){
	if(a.key1<b.key1)return 1;
	if(a.key1>b.key1)return 0;
	if(a.key2<b.key2)return 1;
	if(a.key2>b.key2)return 0;
	return a.i<b.i;
}
void SA(){
	For(i,1,n)rk[i]=s[i]-'0'+1;//预处理长度为1的子串
	for(int l=1;(1<<l)<n;l++){//倍增
		int len=1<<(l-1);//已处理的长度
		For(i,1,n){//处理第一第二关键字以及它所在的后缀位置
			d[i].key1=rk[i];
			d[i].i=i;
			if(i+len<=n)//如果有第二关键字就记录
			d[i].key2=rk[i+len];
			else d[i].key2=0;//没有就将优先级变为最高
		}
		sort(d+1,d+1+n,cmp);//快排
		int rank=1;
		rk[d[1].i]=rank;
		For(i,2,n){//合并第一第二关键字
			if(d[i].key1==d[i-1].key1&&d[i].key2==d[i-1].key2)
			rk[d[i].i]=rank;
			else rk[d[i].i]=++rank;
		}
		if(rank==n)break;//如果排名已经都不相同了就退出循环
	}
	For(i,1,n)sa[rk[i]]=i;
}
signed main(){
	cin>>s;
	s=' '+s;
	n=s.size()-1;
	SA();
	For(i,1,n)printf("%lld ",sa[i]);
	return 0;
}

基数排序

可以直接把上面快排的部分直接换成基数排序。文章来源地址https://www.toymoban.com/news/detail-564447.html

Code

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
int sa[maxn], rank[maxn], newRank[maxn], sum[maxn], key2[maxn];
int n, m;
char  str[maxn];
bool cmp(int a, int b, int l){
	if(rank[a] != rank[b]) return false;
	if( (a+l > n and b+l <= n) or (a+l <= n and b+l > n) ) return false;
	if(a+l > n and b+l > n) return true;
	return rank[a+l] == rank[b+l];

}
int main(){
	scanf("%s", str+1);
	n = strlen(str+1);
	for(int i=1; i<=n; i++) sum[rank[i] = str[i]]++;
	m = max(n, 256);
	for(int i=1; i<=m; i++) sum[i]+=sum[i-1];
	for(int i=n; i>=1; i--) sa[sum[rank[i]]--] = i;
	for(int l=1; l<n; l<<=1){
		int k = 0;
		for(int i=n-l+1; i<=n; i++) key2[++k] = i;
		for(int i=1; i<=n; i++) if(sa[i] > l) key2[++k] = sa[i]-l;
		for(int i=1; i<=m; i++) sum[i] = 0;
		for(int i=1; i<=n; i++) sum[rank[i]]++;
		for(int i=1; i<=m; i++) sum[i]+=sum[i-1];
		for(int i=n; i>=1; i--){
			int j = key2[i];
			sa[sum[rank[j]]--] = j;
		}
		
		int rk = 1;
		newRank[sa[1]] = rk;
		for(int i=2; i<=n; i++){ 
			if(cmp(sa[i-1], sa[i], l)) newRank[sa[i]] = rk;
			else newRank[sa[i]] = ++rk;
		}
		for(int i=1; i<=n; i++) rank[i] = newRank[i];
		
		if(rk == n) break;
	}
	
	for(int i=1; i<=n; i++) printf("%d ",sa[i]);
	return 0;
}

到了这里,关于后缀数组学习笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数组(个人学习笔记黑马学习)

      案例描述: 在一个数组中记录了五只小猪的体重 如: int arr[5] =(300,350,200,400,250): 找出并打印最重的小猪体重。   案例描述: 请声明一个5个元素的数组,并且将元素逆置(如原数组元素为: 1,3,2,5,4;逆置后输出结果为:4,5,2,3,1   作用: 最常用的排序算法,对数组内元素进行排序  

    2024年02月10日
    浏览(44)
  • 【学习笔记】树状数组

    树状数组是一种数据结构,普通树状数组维护的信息及运算要满足结合律且可差分。 树状数组是用长度为 (n) 的数组存储的。我们假设这个数组为 (n) ,令 lowbit(i)=i(-i) ,则 (c_i) 保存的是向前 lowbit(i) 长度的 (a) 数组区间和。 单点加:从 (i) 开始,修改所有包含 (a_i)

    2024年02月15日
    浏览(39)
  • C语言学习笔记:数组

    ✨博文作者:烟雨孤舟 💖 喜欢的可以 点赞 收藏 关注哦~~ ✍️ 作者简介: 一个热爱大数据的学习者 ✍️ 笔记简介:作为大数据爱好者,以下是个人总结的学习笔记,如有错误,请多多指教! 目录 ​​​​​​​ 简介 数组声明 数组初始化 访问数组元素 多维数组 二维数组

    2024年02月09日
    浏览(54)
  • 数据结构学习笔记——多维数组、矩阵

    数组是由n(n≥1)个 相同数据类型 的数据元素组成的有限序列,在定义数组时,会为数组分配一个固定大小的内存空间,用来存储元素,数组在被定义后,其维度不可以被改变。 数组在确定其维度和维界后,元素的个数是固定的,所以不能进行插入和删除运算。数组中最常

    2024年02月03日
    浏览(48)
  • 【C#学习笔记】数组和索引器

    数组具有以下属性: 数组可以是一维、多维或交错的。 创建数组实例时,将建立纬度数量和每个纬度的长度。 这些值在实例的生存期内无法更改。 数值数组元素的默认值设置为0,而引用元素设置为 null。 交错数组是数组的数组,因此其元素为引用类型且被初始化为 null。

    2024年02月14日
    浏览(38)
  • 后缀xls和xlsx有什么区别,xls和xlsx怎么转换

    两种后缀名 都是office excel的生成文件; 其中xls是早期的office生成的文件;office2010之前的版本; xlsx是office2010之后的版本excel生成的文件; office 安装包 含新版本 如果你想相互转化,那就通过另存为,保存对应的后缀名即可; 新建的Excel保存时,需要指定保存类型。目前主流

    2024年02月07日
    浏览(42)
  • 自动驾驶就是在扯?比亚迪你凭什么?

    比亚迪“炮轰”自动驾驶         上周,在比亚迪2022年财报交流会上,有投资人问比亚迪在自动驾驶方面的发展进度和规划,比亚迪集团董事长王传福直言:“无人驾驶那都是扯淡,弄个虚头巴脑的东西那都是忽悠,它就是一场皇帝的新装......自动驾驶一场车祸就让品牌

    2023年04月22日
    浏览(47)
  • MATLAB学习笔记二——元胞数组、结构体

    让我们逐一分析 创建元胞数组 命令行窗口可得结果为 A = 1×6 cell 数组 {0×0 double} {0×0 double} {0×0 double} {0×0 double} {0×0 double} {0×0 double} 数组内矩阵赋值,eye函数用法 结果为 A = 1×6 cell 数组 {0×0 double} {3×3 double} {0×0 double} {0×0 double} {0×0 double} {0×0 double} 数组内矩阵赋值,magi

    2024年02月10日
    浏览(33)
  • IC学习笔记:SystemVerilog队列及数组方法

    队列和数组是SystemVerilog中常用的数据结构,它们可以用来存储和操作一组数据。本文将介绍SystemVerilog中队列和数组的常用方法。 1. 队列方法          队列是一种先进先出(FIFO)的数据结构,它可以用来存储一组有序的数据。 SystemVerilog中的队列有以下常用方法: 1.1 p

    2024年02月16日
    浏览(39)
  • Linux shell编程学习笔记15:定义数组、获取数组元素值和长度

     * 20231103 增加了 五、数组拼接或合并 数组是一种常见的数据结构。跟大多数编程语言一样,大多数Linux shell脚本支持数组,但对数组的支持程度各不相同,比如数组的维度,是支持一维数组还是多维数组?再如,数组元素的下标是从 0 开始还是从1开始?则因shell而异,下面

    2024年02月06日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包