后缀数组(SA)

后缀数组

· 前言

· 定义

后缀数组分为两个数组:
sai 表示第 i 大的后缀的第一个字符在原串中的位置
rki 表示第一个字符在原串中的位置为 i 的后缀的排名。(两个相同的后缀排名相同)

易知:
rksai 表示第 i 名。
sarki 表示 i 这个位置。

· 算法流程

O(nlog2n)做法

我们考虑倍增,已知长度为 xsairki 推长度为 2xsairki
sai

  • 以前 x 个字符为第一关键字。
    x 个字符为第二关键字。

排序的结果。

理解即可
rpt(i,1,n)sa[i]=i;
//此时 sa 的值不是正确的意义但仍可以保证正确 
rpt(i,1,n)rk[i]=s[i];
//因为 rk 的值是正确的 
for(int w=1;w<n;w<<=1){
	//w 为上一组的字符串长度 
	sort(sa+1,sa+1+n,[&](int x,int y){
		return rk[x]==rk[y]?rk[x+w]<rk[y+w]:rk[x]<rk[y];
	});
	//排名离散化 
	int now=0;
	rpt(i,1,n){
		if(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+w]!=rk[sa[i-1]+w])now++;
		tmprk[sa[i]]=now;
		//不能直接修改 rk 
	}
	rpt(i,1,n)rk[i]=tmprk[i];
}
O(nlogn)做法

我们考虑到 rki 的值域并不会超过 n
所以我们把排序换成基数排序即可。

优化前
int v=0;
rpt(i,1,n)v=max(v,(int)s[i]);
// v 表示基数排序值域 
rpt(i,1,n)rk[i]=s[i];

rpt(i,0,v)t[i]=0;
rpt(i,1,n)t[rk[i]]++;
rpt(i,1,v)t[i]+=t[i-1];
rpt(i,1,n)sa[t[rk[i]]--]=i;
//此时 sa 的值需要表示正确的意义
for(int w=1;w<n;w<<=1){
	//w 为上一组的字符串长度 
	rpt(i,0,v)t[i]=0;
	rpt(i,1,n)t[rk[i+w]]++;
    //以 rk[i+w] 为关键字
	rpt(i,1,v)t[i]+=t[i-1];
	rpt(i,1,n)sa[t[rk[i+w]]--]=i;
	//第二关键字排序
	rpt(i,0,v)t[i]=0;
	rpt(i,1,n)id[i]=sa[i];
	rpt(i,1,n)t[rk[id[i]]]++;
    //以 rk[i] 为关键字
	rpt(i,1,v)t[i]+=t[i-1];
	tpr(i,n,1)sa[t[rk[id[i]]]--]=id[i];
    //倒序枚举排名以保证相同大小按原顺序排序
    //sa 会被改变所以用 id 存一下
	//第一关键字排序
	//排名离散化 
	int now=0;
	rpt(i,1,n){
		if(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+w]!=rk[sa[i-1]+w])now++;
		tmprk[sa[i]]=now;
		//不能直接修改 rk 
	}
	v=now;
	rpt(i,1,n)rk[i]=tmprk[i];
}

其实我们会发现上面代码有很多优化空间。

  • 1. 在上一层时 rki 已经有序,所以不需要按第二关键字排序,只需考虑第二关键字为空的情况。
    2.rki 已经互不相同时,说明已经比出大小,直接结束即可。
优化后
for(int w=1;w<n;w<<=1){
	//w 为上一组的字符串长度 
	int top=0;
	rpt(i,n-w+1,n)id[++top]=i;
	rpt(i,1,n)if(sa[i]-w>0)id[++top]=sa[i]-w;
	rpt(i,1,n)sa[i]=id[i];
	//第二关键字其实只需要考虑空串
	//其他非空集已经有序 
	rpt(i,0,v)t[i]=0;
	rpt(i,1,n)id[i]=sa[i];
	rpt(i,1,n)t[rk[id[i]]]++;
	rpt(i,1,v)t[i]+=t[i-1];
	tpr(i,n,1)sa[t[rk[id[i]]]--]=id[i];
	//第一关键字排序
	//排名离散化 
	int now=0;
	rpt(i,1,n){
		if(rk[sa[i]]!=rk[sa[i-1]]||rk[sa[i]+w]!=rk[sa[i-1]+w])now++;
		tmprk[sa[i]]=now;
		//不能直接修改 rk 
	}
	v=now;
	rpt(i,1,n)rk[i]=tmprk[i];
	if(v==n)break;
}

模板

height 数组

height 数组(以下称 hei ) 是后缀数组解决任意两个后缀的最长前缀的手段。
定义 heisaisai1 最长公共前缀长度。

hei 的求法

重要性质:
herkiherki11
证明:

herki1=(sarki1,sarki11)
herki=(sarki,sarki1)
在排好序的后缀中:
sarki1=aAD , sarki11=aAB
则有 sarki=AD
因为后缀 AD 必然存在。
所以 sarki1=AC 且满足 D>CB
即至多会减少 a 一个字符。

posted @   C_Wish  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示