后缀数组及其应用LCP(the Longest Common Prefix,最长公共前缀)

一、前言

后缀数组,\(suffix\) \(array\),缩写\(sa\),在处理字符串问题中用处很多

二、定义

对于一个字符串S,从其第i位字符开始至最后一位所构成的子串,记作后缀\(suffix(i)\),如对于字符串"\(aabaaaab\)",:

suffix(1)="aabaaaab"
suffix(2)="abaaaab"
suffix(3)="baaaab"
suffix(4)="aaaab"
suffix(5)="aaab"
suffix(6)="aab"
suffix(7)="ab"
suffix(8)="b"

显然,对于上述的子串,其处于无序态,我们不妨将其进行排序,将“排序后的第i个后缀的子串在母串中的起始位置”记录下来,保存在后缀数组sa中,

sa[1]=4
sa[2]=5
sa[3]=6
sa[4]=1
sa[5]=7
sa[6]=2
sa[7]=8
sa[8]=3

为了方便编写后续的代码,我们还需要定义排名数组rnk[i],表示排名为i的子串的首位在母串中的起始位置,显然rnksa是互逆的,则有\(rnk[sa[i]]=i,sa[rnk[i]]=i\)

三、算法

显然,如果要求一个字符串的所有后缀数组,朴素算法的复杂度对于OI来说不可接受(将盛有全部后缀字符串的数组进行 sort 排序,由于排序进行 \(O(nlogn)\) 次字符串比较,每次字符串比较要O(n) 次字符比较,所以这个排序是\(O(n^2log n)\)的时间复杂度,其中,n为母串的长度,下同),因此我们需要高效的求出一个字符串的后缀数组,就可以使用以下算法:

DC3([2009] 后缀数组——处理字符串的有力工具 by. 罗穗骞,复杂度\(O(n)\)但是常数大,要在1e6级别的数据中才相对倍增算法有优势
SA-IS(效率最高,但是代码最长

以及我们接下来学习的代码最简单的算法(但是思路挺复杂的):倍增算法

四、思路及部分代码

总体思路

对字符串每个下标开始的长度为\(2^k\)的子串进行排序,以得到排名。k从\(0\)开始,每次增加1,即长度增加一倍,当\(2^k \geq n\)时,从每个下标开始的\(2^k\)的子串都相当于得到了所有后缀。而为了提高时间效率,我们可以由上次子串的排名得到当前子串的排名。

部分代码实现

首先,为了高效排序,在值域和排序对象数量都确定时,我们使用基数排序(的思想),将字符转化为数字存入\(s[]\),并将\(s[]\)赋值给\(x[]\)(相当于\(rnk[]\),之后会作为第一关键字,第二关键字为\(y[]\)

posted on 2026-01-03 17:31  _CENSORED  阅读(4)  评论(0)    收藏  举报

导航