后缀数组-后缀排序

一、一些前置定义:

后缀数组主要指 s a sa sa r k rk rk这两个数组。
字符串下标从 1 1 1 开始。
s u f [ i ] suf[i] suf[i]” (后缀 i i i )表示以第 i i i 个字符为开头的后缀。
s a [ i ] sa[i] sa[i] 表示排名为 i i i 的后缀的编号。
r k [ i ] rk[i] rk[i] 表示后缀 i i i 的排名。

二、后缀数组的求法:

1)纯暴力做法:

s t r i n g string string+ s o r t sort sort.
一次字符串比较的复杂度为 O ( N ) O(N) O(N) ,因此该算法的复杂度为 O ( n 2 l o g n ) O(n^2log n) O(n2logn)

2) O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的做法

该做法运用了倍增的思想。
先从长度为 1 1 1的字符串开始比较,然后对每次比较的字符串长度进行倍增处理。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+60;
int sa[maxn<<1],rk[maxn<<1],oldrk[maxn<<1];
int w=1,n;
char s[maxn];

bool cmp(int x,int y)
{
	return rk[x]==rk[y] ? rk[x+w]<rk[y+w] : rk[x]<rk[y];	
}//以rk[x]为第一关键字,以rk[x+w]为第二关键字

int main()
{
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;++i) sa[i]=i,rk[i]=s[i];
	for(w=1;w<n;w<<=1)
	{
		sort(sa+1,sa+n+1,cmp);
		memcpy(oldrk,rk,sizeof(rk));//因为rk的顺序会被改变,因此应当先把rk放到oldrk中
		for(int i=1,p=0;i<=n;++i)
		{
			if(oldrk[sa[i]]==oldrk[sa[i-1]] && oldrk[sa[i]+w]==oldrk[sa[i-1]+w]) rk[sa[i]]=p;
			else rk[sa[i]]=++p;
		}//去重
	}
	
	for(int i=1;i<=n;++i) printf("%d ",sa[i]);
	return 0;	
}

让我们考虑以下这样做为什么是合理的。
手动模拟以上的过程即可。

例:对于字符串 a b a b abab abab(fxj语十级)
s u f [ 1 ] = a b a b , s u f [ 2 ] = b a b , s u f [ 3 ] = a b , s u f [ 4 ] = b suf[1]=abab,suf[2]=bab,suf[3]=ab,suf[4]=b suf[1]=abab,suf[2]=bab,suf[3]=ab,suf[4]=b
先对进行一次 w = 1 w=1 w=1的排序。
得到从小到大的顺序 a b a b abab abab, a b ab ab, b a b bab bab, b b b
在进行一次 w = 2 w=2 w=2的排序。
得到从小到大的顺序 a b a b abab abab, a b ab ab, b b b, b a b bab bab
再进行一次w=4的排序
得到从小到大的顺序 a b ab ab, a b a b abab abab, b b b, b a b bab bab

总结
倍增比较成立的根本原因是字符串比较的字典序中先比较前面的字符再比较后面的字符,最后比较长度。即如果一个字符串 a a a的前面任意一位z i i i大于另一个字符串 b b b的相同位置,则字符串 a a a一定大于字符串 b b b,后面的剩余部分不需要比较。

3)优化做法:

该算法的瓶颈主要在于排序,因此优化排序就能突破这一瓶颈。
可以用基数排序和计数排序优化到 O ( n l o g n ) O(nlogn) O(nlogn) 甚至是 O ( n ) O(n) O(n)
由于我并没有研究过基数排序因此这一优化将会推迟至我搞完基数排序
以及我觉得上面这个证明方式是真的太草率了因此一定会尽快补上严谨证明的!

三、 h e i g h t height height数组

1) L C P LCP LCP(最长公共前缀)

两个字符串 S S S T T T就是最大的 x x x ( x ≤ m i n ( ∣ S ∣ , ∣ T ∣ ) ) (x\le min(|S|,|T|)) (xmin(S,T)),使得 S i S_i Si = = = T i T_i Ti   ( ∀   1 ≤ i ≤ x ) \ (\forall\ 1\le i \le x)  ( 1ix)
下文以 l c p ( i , j ) lcp(i,j) lcp(i,j) 表示后缀 i i i 和 后缀 j j j 的最长公共前缀(的长度)。

2) h e i g h t height height数组的定义

h e i g h t [ i ] height[i] height[i]= l c p ( s a [ i ] , s a [ i − 1 ] ) lcp(sa[i],sa[i-1]) lcp(sa[i],sa[i1]),即第 i i i名的后缀与它前一名的后缀的最长公共前缀。
h e i g h t [ i ] = 0 height[i] = 0 height[i]=0。(可以看作一个规定,实际上并不存在 h e i g h t [ 1 ] height[1] height[1])。

3) O ( n ) O(n) O(n) h e i g h t height height数组需要的一个引理

h e i g h t [ r k [ i ] ] ≥ h e i g h t [ r k [ i − 1 ] ] − 1 height[rk[i]] \ge height[rk[i-1]]-1 height[rk[i]]height[rk[i1]]1

翻译一下,后缀 i i i 与后缀 s a [ r k [ i ] − 1 ] sa[rk[i]-1] sa[rk[i]1] 的最长公共前缀长度大于等于后缀 s a [ r k [ i − 1 ] ] sa[rk[i-1]] sa[rk[i1]] 与后缀 s a [ r k [ i ] − 2 ] sa[rk[i]-2] sa[rk[i]2] 的最长公共前缀长度 − 1 -1 1

证明:
先分类讨论
h e i g h t [ r k [ i − 1 ] ] ≤ 1 height[rk[i-1]]\leq1 height[rk[i1]]1时,
上面的式子显然成立,因为右边小于等于 0 0 0,而左侧一定大于等于 0 0 0
h e i g h t [ r k [ i − 1 ] > 1 height[rk[i-1]>1 height[rk[i1]>1 时,
设后缀 i − 1 i-1 i1 a A D aAD aAD ( ( (A是一个长度为 h e i g h t [ r k [ i − 1 ] ] − 1 height[rk[i-1]]-1 height[rk[i1]]1的字符串 ) ) ),那么后缀 i i i就是 A D AD AD。设后缀 s a [ r k [ i − 1 ] − 1 ] sa[rk[i-1]-1] sa[rk[i1]1] a A B aAB aAB,那么 l c p ( i − 1 , s a [ r k [ i − 1 ] − 1 ] ) = a A lcp(i-1,sa[rk[i-1]-1])=aA lcp(i1,sa[rk[i1]1])=aA。那么由于后缀 s a [ r k [ i − 1 ] − 1 ] + 1 sa[rk[i-1]-1]+1 sa[rk[i1]1]+1 A B AB AB,一定排在后缀 i i i前面,那么后缀 s a [ r k [ i ] − 1 ] sa[rk[i]-1] sa[rk[i]1]一定含有后缀 A A A,所以
l c p ( i , s a [ r k [ i ] − 1 ] ) lcp(i,sa[rk[i]-1]) lcp(i,sa[rk[i]1])至少是 h e i g h t [ r k [ i − 1 ] − 1 height[rk[i-1]-1 height[rk[i1]1

直观地表现一下:
i − 1 = a A D i-1=aAD i1=aAD
i = A D i=AD i=AD
s a [ r k [ i − 1 ] − 1 ] = a A B sa[rk[i-1]-1]=aAB sa[rk[i1]1]=aAB
s a [ r k [ i − 1 ] − 1 ] + 1 = A B sa[rk[i-1]-1]+1=AB sa[rk[i1]1]+1=AB
s a [ r k [ i ] − 1 ] = A [ B / C ] sa[rk[i]-1]=A[B/C] sa[rk[i]1]=A[B/C]
l c p ( i , s a [ r k [ i ] − 1 ] ) = A X ( X 可 能 为 空 ) lcp(i,sa[rk[i]-1])=AX(X可能为空) lcp(i,sa[rk[i]1])=AX(X)

4) O ( n ) 求 h e i g h t 数 组 − C o d e O(n)求height数组-Code O(n)heightCode

for(int i=1,k=0;i<=n;++i)
{
	if(k) --k;
	while(s[i+k]==s[sa[rk[i]-1]+k]) ++k;
	ht[rk[i]]=k;
}

k ≤ n k\le n kn,最多减 n n n次,所以最多加 2 n 2n 2n,总复杂度就是 O ( n ) O(n) O(n)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值