首页 > 其他分享 >后缀数组 I —— 后缀排序

后缀数组 I —— 后缀排序

时间:2023-01-09 14:46:12浏览次数:53  
标签:后缀 关键字 数组 字符串 排名 排序 rk

后缀数组(suffix array)是省选字符串题目中非常重要的算法。

本文将简略讲述其 \(O(n\log n)\) 求法,对于时间复杂度更优秀但 not practical 的做法不作提及。

模板

考虑一种字符串比较大小的新方式。

对于长度为 \(n\) 的字符串 \(s1,s2\),我们考虑先比较其前 \(k\) 位,若前 \(k\) 位已经能比较出大小,则比较成功,否则再比较其后 \(n-k\) 位。

这种比较方式的正确性很显然,但我们要考虑其带给我们的启示。

我们这个比较方式实际上将一个大的问题分解为了两个相对较小的问题,这实际上是一种分治。

那么,基于这种分治思想的后缀数组便应运而生了。

其采用倍增的思路,实际上很容易理解,因为分成的两个相对较小的子问题的复杂度应相对平均,故实际上最优的 \(k\) 在 \(\frac{n}{2}\) 时取到。

那么将 \(k=\frac{n}{2}\) 带入原来的思路就会得到一种显而易见的倍增,倍增的过程即是 \(k\) 从小到大,将所有长度为 \(2^{k-1}\) 的比较完了过后再进行 \(2^k\) 的比较,而边界条件则是 \(k=0\),此时的单字符比较显然更容易。

在两个字符串比较时,两个字符串的关系可以用简单的小于等于大于来表示,但后缀数组要解决的问题则是多个字符串的比较,此时如何表示大小关系?

显然我们可以采用排名的方式,记录每个字符串排序后在有序的字符串序列中的位置,以此作为大小关系的表示方式,特别的,当两个字符串相等时,其排名也相等。

由此,后缀排序也如两个字符串的比较一样呼之欲出。

设字符串 \(s\) 的第 \(l\) 位至第 \(r\) 位构成的子串为 \(s[l\sim r]\)。

使用后缀数组进行长度为 \(2^k\) 的所有字符串排序时,被排序的字符串为 \(s[1\sim2^k],s[2\sim2^k+1],s[3\sim2^k+2]\cdots\cdots\)。

在比较 \(s[x\sim2^k+x-1]\) 和 \(s[y\sim2^k+y-1]\) 时,由于我们已经将所有长度为 \(2^{k-1}\) 的所有字符串的排名求出来的,记 \(s[z\sim2^{k-1}+z-1]\) 对应的排名为 \(rk_z\),则这两个字符串的比较方式为:

  • 先比较 \(rk_x\) 和 \(rk_y\) 的大小,若 \(rk_x\ne rk_y\) 则比较结果直接可得出。

  • 若 \(rk_x=rk_y\) 则再继续比较 \(rk_{x+2^{k-1}}\) 和 \(rk_{y+2^{k-1}}\) 即可。

需要注意的是,如果两次比较后仍相同,它们的排名也需保持相同。

由此,后缀数组的过程便完成了,但如何进行字符串的排序时我们需思考的。

观察一下多个字符串比较的本质,我们发现其本质上为一个双关键字排序

考虑朴素做法,即暴力 \(O(n\log n)\) 排序,加上倍增的 \(O(\log n)\),总复杂度 \(O(n\log^2n)\),在 \(10^6\) 的数据范围下通过较为吃力。

那么可以想见我们需要一种更快的排序,能够在 \(O(n)\) 的时间复杂度内完成双关键字排序。

考虑基数排序

我们将一个包含双关键字的字符串看做一个二元组。

下面将简述过程:

\(1.\) 将所有第二关键字进行桶排序,由此得到按照第二关键字大小排列的二元组顺序。

\(2.\) 将所有第一关键字进行桶排序,并进行前缀和以获得当前排名(后面排名会不断刷新,此处的当前排名对应的是第一关键字为它且第二关键字最大的二元组)。

\(3.\) 倒序枚举根据第二关键字大小排列的二元组(倒序枚举保证了第二关键字有序),每枚举到一个二元组便将排名赋为对应桶上的排名并将该桶对应的排名 \(-1\)(倒序枚举的用处体现,对于同第一关键字的二元组,先被枚举的排名一定大于等于后被枚举的排名,因为第二关键字有序)。

由此,基数排序便完成了。

但我们发现,如此排完一次序后,所有二元组的排名将互异,但却存在两个二元组(字符串)相同的情况,怎么办?

考虑判断排序后相邻二元组是否相等,即赋排名时若该二元组与上一个二元组相同,则排名等于上一个二元组的排名,否则排名等于上一个二元组的排名 \(+1\)。

终于,我们解决了后缀排序问题。

代码
#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff
#define timeused() (double)clock()/CLOCKS_PER_SEC
#define rep(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i)
#define repp(i,a,b) for(register int i=a,i##end=b;i>=i##end;--i)
#define mp make_pair
#define pb push_back
typedef int ll;
typedef unsigned long long ull;
template<typename T> inline T rd(T& x){
    T f=1;x=0;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(T)(c-'0');
    x*=f;
	return x;
}
string s;
ll n,rk[4000005],nw[1000005],sa[1000005];
ll a[1000005],tp[1000005];
void sufsrt(){
	rep(i,0,200) a[i]=0;
	rep(i,1,n) ++a[(ll)s[i]];
	rep(i,1,200) a[i]+=a[i-1];
	repp(i,n,1) sa[a[(ll)s[i]]--]=i;
	rk[sa[1]]=1;
	rep(i,2,n){
		rk[sa[i]]=rk[sa[i-1]];
		if(s[sa[i]]!=s[sa[i-1]]) ++rk[sa[i]];
	}
	for(int i=1;(1<<(i-1))<=n;++i){
		rep(j,0,n) a[j]=0; 
		rep(j,1,n) ++a[rk[j+(1<<(i-1))]];
		rep(j,1,n) a[j]+=a[j-1];
		rep(j,1,n) tp[a[rk[j+(1<<(i-1))]]--]=j;
		rep(j,0,n) a[j]=0;
		rep(j,1,n) ++a[rk[j]];
		rep(j,1,n) a[j]+=a[j-1];
		repp(j,n,1) sa[a[rk[tp[j]]]--]=tp[j];
		nw[sa[1]]=1;
		rep(j,2,n){
			nw[sa[j]]=nw[sa[j-1]]+1;
			if(rk[sa[j-1]]==rk[sa[j]]&&rk[sa[j-1]+(1<<(i-1))]==rk[sa[j]+(1<<(i-1))]) --nw[sa[j]];
		}
		rep(j,1,n) rk[j]=nw[j];
	}
}
int main(){
	cin>>s;
	n=s.size();
	s=' '+s;
	sufsrt();
	rep(i,1,n) printf("%d ",sa[i]);  
}

标签:后缀,关键字,数组,字符串,排名,排序,rk
From: https://www.cnblogs.com/Miracle-blog/p/17036987.html

相关文章

  • Json-Tutorial05 数组解析
    前言本节将要学习的是第一种复合类型的解析:数组。具体的解析规则在Tutorial中已经有了,概括下简单的思想就是遇到[符号之后挨个调用lept_parse_value来解析数组的每一个元......
  • 4655. 重新排序
    4655.重新排序给定一个数组A和一些查询Li,Ri,求数组中第Li至第Ri个元素之和。小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能......
  • ES6-解构赋值(数组)
    一.概念:解析某一数据的结构,将我们想要的东西提取出来,赋值给变量或者常量1const[a,b,c]=[1,2,3];2console.log(a,b,c);//123二.数组的解构......
  • 数组排序
    /***数组元素交换位置*@param{array}arr数组*@param{number}index1添加项目的位置*@param{number}index2删除项目的位置*index1和index2分别是两个数组......
  • 关于快速排序算法最多比较次数与最少比较次数的问题
    关于快速排序算法最多比较次数与最少比较次数的问题最常见的快速排序算法的衡量标准是时间复杂度,即最坏情况\(O(n)\),最优与平均情况均为\(O(n\log_2^n)\)。最近看到......
  • 选择&冒泡&插入排序以及交换两数的三种方式
    选择排序//0~n位先排第0位的,将1~n的分别与0上的比较,如果小于它,交换//再排第1位,将2~n的分别与0上的比较,如果小于它,交换//以此类推publicstaticvoidselectSo......
  • 数组模拟环形队列
    图示:代码:1importjava.util.Scanner;23publicclassRingQueueTest{4publicstaticvoidmain(String[]args){5//创建一个环形队......
  • 【LeetCode数组#5行为模拟】螺旋矩阵II
    螺旋矩阵II力扣题目链接(opensnewwindow)给定一个正整数n,生成一个包含1到n^2所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。示例:输入:3输出:[[1,......
  • 客服系统前端开发:JavaScript删除对象数组中指定key value的对象【唯一客服】网页在线
    经常我们有这样的需要,比如有一个对象数组,我们要把这个数组里某个对象删除掉,根据他的某一个key的value来删除可以使用JavaScript的filter()方法来删除对象数组中指定k......
  • 插入排序10-3
    ///<summary>///插入排序///从第2个数开始,跟第一个数对比,放在左边还是右边///循环下去比较,都放在合适的位置///</summary>///<paramname="arr"></param>publi......