后缀数组(入门) Suffix array【基数排序+倍增】

目录

  • 参考的相关链接
  • 什么是基数排序
  • 什么是倍增
  • 什么是后缀数组
  • 模板题 [P3809 【模板】后缀排序](https://www.luogu.com.cn/problem/P3809)
  • 代码

参考的相关链接

  • 后缀数组的构建思想视频:Suffix array简介和构建
  • 基数排序的思想及代码解释视频:排序算法详解(七)基数排序
  • 倍增思想
  • 后缀数组:这个和这个

什么是基数排序

  • 基数排序是桶排序的扩展
  • 按个位、十位、百位依次进行排序
  • 举个例子(图片来自这里)
    后缀数组(入门) Suffix array【基数排序+倍增】_第1张图片
  • 具体详解看代码及注释
#include
void radixsort(int *a,int len)
{
     
	int max=a[1];
	for(int i=2;i<=len;i++)
		if(a[i]>max)max=a[i];//找出数组中做大的数
	int base=1;
	while(max/base>0)//最大数有几位就比较几次 
	{
     
		int bucket[10]={
     0};//设置0-9十个桶 
		for(int i=1;i<=len;i++)
			bucket[a[i]/base%10]++;//将数分配进桶里 
		for(int i=1;i<10;i++)
			bucket[i]+=bucket[i-1];//累加以确定桶中元素在原数组的位置
		int t[100];//临时数组 
		for(int i=len;i>=1;i--)
		{
     
			t[bucket[a[i]/base%10]]=a[i];
			bucket[a[i]/base%10]--;	 
		}
		for(int i=1;i<=len;i++)
			a[i]=t[i];//将桶中元素收集回原数组	
		base*=10;
	} 
}
int main()
{
     
	int n,a[100];
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	radixsort(a,n);
	for(int i=1;i<=n;i++)printf("%d ",a[i]);
	return 0;
} 
  • 用在求后缀数组中:

按照字典序排列,从第一位开始比,先按每个后缀的第一个字符排个序,(相当于把这个字符串的每个字符排个序)

除非正好每个字符不一样,否则肯定有部分的字符是排名是一样的.

按照排序,接下来要比较这些排名一样的字符的第二位!

其实,第二位我们已经比较过了!第 i 个后缀的第二个字符就是第 i+1个字符,也就是已经排序好了。

而第一位是第一关键字,第二位是第二关键字。

什么是倍增

  • 倍增,顾名思义就是一倍一倍的增加。举个例子你每次可以跳2的k次方步。现在你要跳到距离7步的地方。

    跳8步 跳过了(不跳)

    跳4步 没跳到(跳)

    跳2步 没跳到 (跳)

    跳1步 跳到 (停)

    这里就只跳了3次 比普通的7次跳发优化了4次;

    如果数据很大就是O(n) 和 O(logn)的区别了。

  • 用在求后缀数组中:先计算每个点开始向后 2k 个字符的排名,两两拼接,算出 2k+1个字符的排名,直到区分出所有后缀。

什么是后缀数组

  1. 一个字符串有n个后缀分别是从0、1、……、n-1开始到n-1的子串,现要对这些子串进行排名
  2. 后缀数组就是sa表示排名为i的后缀的起始位置
  3. 其他需用到的数组
    rk[i]:位置为i的后缀的排名 第一关键字
    tp[i]:第二关键字的排名为i的后缀的位置 还被用做rk的暂存
    tax[i]:排名为i的后缀数量 即“桶 ”
  4. 性质
    rk[sa[i]]=i sa[rk[i]]=i

模板题 P3809 【模板】后缀排序

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

  • 1≤n≤106 暴力求解会超时
  • 具体详解看代码及注释

代码

#include
#include
#include
using namespace std;
int const maxn=1e6+6;
int sa[maxn],rk[maxn],tp[maxn],tax[maxn];
int n,m,p;
char s[maxn];
/*
sa[i]:排名为i的后缀的位置 
rk[i]:位置为i的后缀的排名   第一关键字 
tp[i]:第二关键字的排名为i的后缀的位置   还被用做rk的暂存 
tax[i]:排名为i的后缀数量  即“桶 ” 
*/
void radixsort()
{
     
	for(int i=1;i<=m;i++)tax[i]=0;//把桶清空 
	for(int i=1;i<=n;i++)tax[rk[i]]++;//分配
	for(int i=2;i<=m;i++)tax[i]+=tax[i-1];
	for(int i=n;i>=1;i--)sa[tax[rk[tp[i]]]--]=tp[i];
	//在第一关键字相同的情况下排第二关键字
	//((第二关键字排名为i的后缀的位置)的桶编号)的前缀和排名
} 
void Sort()
{
     
	for(int i=1;i<=n;i++)
	{
     
		rk[i]=s[i];//初始化长度为1的rk即每个字符的排名为其ascall码的值 
		tp[i]=i;
	}
	radixsort();//计算出长度为1的sa
	for(int k=1;k<=n;k<<=1)//采用倍增思想 
	{
     
		p=0;//p是计数器,记录有多少个后缀的排名不同
		for(int i=n-k+1;i<=n;i++)tp[++p]=i;
		//对于起始位置在[n-k+1,n]的后缀,它们没有第二关键字,所以他们的第二关键字最小
		for(int i=1;i<=n;i++)//确定tp 
			if(sa[i]>k)//若第i位开始的后缀可作其他位置的第二关键字 
				tp[++p]=sa[i]-k;//把它放到对应的第一关键字上 
		// 编号越小表示这个后缀的起始位置越靠前
        // 我们的第二关键字要从小到大,而我们正好是按照从小到大的顺序枚举每一个后缀
		radixsort();//更新长度为1+k的后缀的sa
		swap(rk,tp); //此时tp已经无用,直接备份上一个rk
		//接下来更新rk 
		rk[sa[1]]=p=1;// 排名第一的后缀直接加入
		for(int i=2;i<=n;i++)//此时的tp是rk 
			if(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+k]==tp[sa[i-1]+k])
				rk[sa[i]]=p;
			else{
     
				p++;rk[sa[i]]=p;
			} 
			//若与前一个的第一关键字和第二关键字排名都相同则合并为同一个排名 
		if(p==n)break;//当数量为n时,完成区分排名
		m=p;//改变桶的大小为排名的数量
	}
}
int main()
{
     
	scanf("%s",s+1);
	n=strlen(s+1);
	m=127;//char的ascall码最大为127即为桶的最大数目
	Sort();
	for(int i=1;i<=n;i++)printf("%d ",sa[i]); 
	return 0;
} 

你可能感兴趣的:(学习笔记)