后缀数组

合集下载

【字符串】后缀数组

【字符串】后缀数组

【字符串】后缀数组后缀排序倍增算法n字符串的长度。

m当前后缀(离散化后)的值域。

对于char可以跳过离散化,初值取128即可,对于int要离散化,初值取n即可,初值要保证覆盖整个值域。

sa[i]排名为i的后缀的起始位置。

rk[i]起始位置为i的后缀的排名。

验证:const int MAXN = 1000000 + 10;int n, m, ct[MAXN], tp[MAXN];int sa[MAXN], rk[MAXN], ht[MAXN];void RadixSort() {for(int i = 0; i <= m; ++i)ct[i] = 0;for(int i = 1; i <= n; ++i)++ct[rk[i]];for(int i = 1; i <= m; ++i)ct[i] += ct[i - 1];for(int i = n; i >= 1; --i)sa[ct[rk[tp[i]]]--] = tp[i];}bool Compare(int i, int j, int l) {if(tp[sa[i]] == tp[sa[j]]) {if(sa[i] + l <= n && sa[j] + l <= n) {if(tp[sa[i] + l] == tp[sa[j] + l])return 1;}}return 0;}void SuffixSort(char *s) {n = strlen(s + 1), m = 128;for(int i = 1; i <= n; ++i) {rk[i] = s[i];tp[i] = i;}RadixSort();for(int l = 1;; l <<= 1) {m = 0;for(int i = n - l + 1; i <= n; ++i)tp[++m] = i;for(int i = 1; i <= n; ++i) {if(sa[i] > l)tp[++m] = sa[i] - l;}RadixSort();swap(tp, rk);m = 1;rk[sa[1]] = 1;for(int i = 2; i <= n; ++i) {if(Compare(i - 1, i, l) == 0)++m;rk[sa[i]] = m;}if(m == n)break;}}最⼩循环表⽰把字符串S循环移动,找字典序最⼩的那个表⽰。

后缀数组最详细讲解

后缀数组最详细讲解

后缀数组最详细讲解转载⾃后缀数组最详细(maybe)讲解后缀数组这个东西真的是神仙操作……但是这个⽐较神仙的东西在⽹上的讲解⼀般都仅限于思想⽽不是代码,⽽且这个东西开⼀堆数组,很多初学者写代码的时候很容易发⽣歧义理解,所以这⾥给出⼀个⽐较详细的讲解。

笔者⾃⼰也是和后缀数组硬刚了⼀个上午外加⼀个中午才理解的板⼦。

本⼈版权意识薄弱,如有侵权现象请联系博主邮箱xmzl200201@参考⽂献:以下是不认识的dalao们:特别感谢以下的两位dalao,写的特别好,打call什么是后缀数组我们先看⼏条定义:⼦串在字符串s中,取任意i<=j,那么在s中截取从i到j的这⼀段就叫做s的⼀个⼦串后缀后缀就是从字符串的某个位置i到字符串末尾的⼦串,我们定义以s的第i个字符为第⼀个元素的后缀为suff(i)后缀数组把s的每个后缀按照字典序排序,后缀数组sa[i]就表⽰排名为i的后缀的起始位置的下标⽽它的映射数组rk[i]就表⽰起始位置的下标为i的后缀的排名简单来说,sa表⽰排名为i的是啥,rk表⽰第i个的排名是啥⼀定要记牢这些数组的意思,后⾯看代码的时候如果记不牢的话就绝对看不懂后缀数组的思想先说最暴⼒的情况,快排(n log n)每个后缀,但是这是字符串,所以⽐较任意两个后缀的复杂度其实是O(n),这样⼀来就是接近O(n^2 log n)的复杂度,数据⼤了肯定是不⾏的,所以我们这⾥有两个优化。

ps:本⽂中的^表⽰平⽅⽽不是异或倍增⾸先读⼊字符串之后我们现根据单个字符排序,当然也可以理解为先按照每个后缀的第⼀个字符排序。

对于每个字符,我们按照字典序给⼀个排名(当然可以并列),这⾥称作关键字。

接下来我们再把相邻的两个关键字合并到⼀起,就相当于根据每⼀个后缀的前两个字符进⾏排序。

想想看,这样就是以第⼀个字符(也就是⾃⼰本⾝)的排名为第⼀关键字,以第⼆个字符的排名为第⼆关键字,把组成的新数排完序之后再次标号。

没有第⼆关键字的补零。

后缀数组代码详解

后缀数组代码详解

后缀数组代码详解直接解释代码#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define N 100001char ch[N];int n,k,a[N],v[N],p,q=1,sa[2][N],rk[2][N],h[N];void mul(int *sa,int *rk,int *SA,int *RK)//sa,rk是上⼀轮基数排序的结果,SA,RK是本轮结束后基数排序的结果// 把后缀从前往后编号,sa[i]排第i名(排名即字典序⼤⼩)的后缀是谁,rk[i]i号后缀的排名是多少{for(int i=1;i<=n;i++) v[rk[sa[i]]]=i;//v⽤另⼀种⽅法统计了前缀和,i是排名,v[j]=i为排名为1——j的数⼀共有i个for(int i=n;i;i--)if(sa[i]>k) //只有sa[i]>k,i才能既是sa[i]的第⼀关键字排名,⼜是sa[i]-k的第⼆关键字排名SA[v[rk[sa[i]-k]]--]=sa[i]-k;//倍增之后新的sa数组for(int i=n-k+1;i<=n;i++) //n-k之后的⼆元组⽆第⼆关键字,排在所属前缀和范围的最前⾯SA[v[rk[i]]--]=i;for(int i=1;i<=n;i++) //倍增之后新的rk数组RK[SA[i]]=RK[SA[i-1]]+(rk[SA[i]]!=rk[SA[i-1]]||rk[SA[i]+k]!=rk[SA[i-1]+k]);//对⽐两个关键字}void presa(){for(int i=1;i<=n;i++) v[a[i]]++;//相同字母的个数(相同的后缀起始位置)for(int i=1;i<=26;i++) v[i]+=v[i-1];//前缀和,为设定初始排名(即只对⼀个字母的排名)做准备for(int i=1;i<=n;i++)sa[p][v[a[i]]--]=i;//有了前缀和数组v后,数i的排名便在v[i-1]+1——v[i]之间,实际上v[i]-v[i-1]个数的排名相等,这⾥不设为相等for(int i=1;i<=n;i++)rk[p][sa[p][i]]=rk[p][sa[p][i-1]]+(a[sa[p][i-1]]!=a[sa[p][i]]);//sa数组与rk数组相反,sa[i]=j表⽰排名为i的后缀起始位置为j,rk[i]=j表⽰起始位置为i的后缀排名为jfor(k=1;k<n;k<<=1,swap(p,q))//真正开始倍增排序//p所代表的的是上⼀轮信息,q代表的是要进⾏的本轮排序信息//上⼀轮基数排序的结果,是本轮基数排序的要利⽤的信息,所以p,q交换mul(sa[p],rk[p],sa[q],rk[q]);//求height数组://height[i]:排名为i的后缀和排名为i-1的后缀的最长公共前缀//设h[i]表⽰i和rk[i]-1的最长公共前缀,即i号后缀和排在i号后缀前⼀名的后缀的最长公共前缀//那么有⼀个性质:h[i]>=h[i-1]-1//注:字符串中,i号后缀的顺序在i-1号后缀后⾯,排名就不⼀定了,后⾯的排在XX哪⼉均指排名//证明://若h[i-1]=1,显然成⽴//若h[i-1]>1,设排名为rk[i-1]-1的后缀为k号后缀,即后缀k排在后缀i-1的前⼀位//那么h[i-1]=后缀i-1和后缀k的最长公共前缀,显然他们⾄少前两个字符相同//i号后缀即为i-1号后缀去掉第⼀个字母,k+1号后缀即为k号后缀去掉第⼀个字母//显然k+1号后缀⼀定排在i号后缀的前⾯,否则k号后缀就不会排在i-1号后缀的前⾯//若k+1号后缀恰好排在i号后缀的前⼀名,显然成⽴//否则,设排在i好后缀的前⼀名是L号后缀,L号后缀⼀定在k+1号后缀的后⾯//L号后缀和i号后缀的最长公共前缀>=k+1号后缀和i号后缀的最长公共前缀//因为i号后缀和k+1号后缀的最长公共前缀⼀定是在L的前缀,否则L不会排名在i和k+1的中间;L号后⾯还会有可能继续与i号匹配 //所以h[i]>=h[i-1]-1,即i号后缀和排在i号前⼀名的后缀的最长公共前缀>=i-1号后缀和排在i-1号前⼀名的后缀的最长公共前缀-1 for(int i=1,k=0;i<=n;i++){int j=sa[p][rk[p][i]-1];//p:虽然倍增过程中新的⼀轮排序信息存在q⾥,但结束后还要执⾏swap,所以⽤p⾥的信息while(a[i+k]==a[j+k]) k++;h[rk[p][i]]=k;if(k) k--;//这⾥的h数组指的是上⾯说的height数组,上⾯的h数组的信息是⽤来辅助计算height的,可以不⽤存}}int main(){scanf("%s",ch+1);n=strlen(ch+1);for(int i=1;i<=n;i++) a[i]=ch[i]-'a'+1;//字符串转化为数字presa();for(int i=1;i<=n;i++) printf("%d ",sa[p][i]);puts(""); for(int i=2;i<=n;i++) printf("%d ",h[i]);}。

后缀树与后缀数组

后缀树与后缀数组

• 显然, LCP(Suffix(i+1), Suffix(j+1)) = max(h[k]-1,0);
i i+1
j j+1
• 设i+1在sa中位置为t,sa[t+1] = p 即h[t] = LCP(suffix(i+1),suffix(p)) • 由suffix(i) < suffix (j) => suffix(i+1) < suffix(j+1) • 而suffix(p) 在sa数组中的位置紧贴着suffix(i+1),所以有 suffix(i+1) < suffix(p) <= suffix(j+1) • 而LCP(suffix(i+1),suffix(j+1)) = max(h[k]-1,0) 下标:1 2 3 4 5 Sa数组
– 当i超过字符串的长度,可以认为s[i] = -oo。
• 后缀:指从某个位置i开始到整个串结束的一个 特殊子串。字符串S的从i个字符开始的后缀记为 Suffix(i)。
– 显然,Suffix(i) = S[i..len(S)],记为S(i)
• 字符串的大小比较:例如串S与串T,从小到大 枚举i,如果s[i] < t[i] => S < T, 如果s[i] > t[i] => S > T。 两个串完全匹配则S== T
• 名次数组:名次数组Rank[i]保存的是 Suffix(i) 在所有后缀中从小到大排 列的“名次”。 可以视为大小
– 简单来说,名次数组就是问“你排第几”
• 显然,两者只要知道一个,就可以推出另外 一个
下标:1
2

软考知识点2

软考知识点2

20、后缀数组(1)什么是后缀数组呢?直观来说,后缀数组是记录一个字符串的后缀的排名的数组,什么是后缀呢,设一个字符串的长度是len(我们约定字符串下标从0开始,所以到len-1结束,比较符合我们日常编程习惯),某一位置i的后缀的就是从i开始到len-1结束的字符串,用suffix(i)表示,即对字符串s来说,suffix(i) =s[i,i+1....len-1],可以发现不同的后缀按字典序排列的名词是不一样的(什么是字典序都应该知道吧),记录这些后缀按字典序排好的结果的数组就叫后缀数组,一般用sa[]表示,网络上对sa[]的标准定义为:后缀数组:后缀数组SA 是一个一维数组,它保存1..n 的某个排列SA[1],SA [2],……,SA[n],并且保证Suffix(SA[i])< Suffix(SA[i+1]),1≤i< 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA>另外还要用到排名数组,即某一位置的后缀在所有后缀中的排名的数组,一般用Rank[]表示,容易发现Rank[sa[i]]=i。

名次数组:名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排列的“名次”。

简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。

知道了这些定义,剩下的就是如何构建后缀数组了,可以按照定义来构建,把每个后缀当做一个字符串,用全速排序来排序,不过那样的时间复杂度为O(n*n),一般用来构建后缀数组用的是倍增算法(Doubling Algorithm),说到倍增算法,就要说到k-前缀的定义,字符串u的k-前缀就是u的从0开始到k-1的字串,u长度不足k时就是整个字符串u,这样一来我们在比较串s的两个位置i,j的后缀的2k-前缀时,就是比较两个后缀的k-前缀和两个后缀位置+k的k-前缀,显然当k=1时就是对整个串的单字符进行排序,复杂度O(nlogn),当k>=2时对已排好的k-前缀进行排序,用快排,复杂度O(nlogn),用基数排序,复杂度O(n),容易发现k是2倍增的。

数据结构编译原理 前缀和后缀

数据结构编译原理 前缀和后缀

数据结构编译原理前缀和后缀前缀和和后缀分别是一种常用的数据结构和算法,它们在编译原理和其他一些计算机科学领域中都有广泛的应用。

前缀和是一种用来统计数组前缀和区间和的算法,也可以叫做累加和。

它的实现方式是先将原数组中的值依次相加得到一个新数组,然后在新数组中查询任意区间的和时,只需要进行一次减法操作即可得到区间和。

例如,给定一个数组arr,前缀和数组prefixSum 的计算方式如下所示:prefixSum[0] = arr[0];for(int i = 1; i < n; i++){prefixSum[i] = prefixSum[i-1] + arr[i];}其中,n是数组的长度。

例如,如果arr=[1,2,3,4,5],则prefixSum=[1,3,6,10,15]。

要查询arr[1]到arr[3]的区间和,只需要进行一次减法,即prefixSum[3]-prefixSum[0]=10-1=9。

前缀和的时间复杂度为O(n),空间复杂度也为O(n),适用于数组多次查询区间和的情况。

需要注意的是,在前缀和计算过程中,可能会出现整型溢出的情况,因此需要根据具体情况选择使用long long等数据类型来避免这种问题。

后缀数组是一种用来快速匹配字符串模式的算法。

它的实现方式是将原字符串的所有后缀按照字典序排序,然后记录每个后缀在排序后的后缀数组中的位置。

例如,给定一个字符串str,后缀数组suffixArray的计算方式如下所示:例如,如果str="banana",则suffix=["banana","anana","nana","ana","na","a"],suffixArray=[5,3,1,0,4,2]。

可以通过对suffixArray数组进行二分查找来快速匹配包含某个字符串模式的后缀。

后缀数组入门(一)——后缀排序

后缀数组入门(一)——后缀排序

后缀数组⼊门(⼀)——后缀排序前⾔后缀数组这个东西早就有所⽿闻,但由于很难,学了好⼏遍都没学会。

最近花了挺长⼀段时间去研究了⼀下,总算是勉强学会了⽤倍增法来实现后缀排序(据说还有⼀种更快的DC3法,但是要难得多)。

数组定义⾸先,为⽅便起见,我们⽤后缀i表⽰从下标i开始的后缀。

(相信⼤家都知道后缀是什么的)⾸先,我们需要定义⼏个数组:s:需要进⾏后缀排序的字符串。

SA i:记录排名为i的后缀的位置。

rk i:记录后缀i的排名。

pos i:同样记录排名为i的后缀的位置。

tot i:⽤于基数排序,统计i的排名。

要注意理解这些数组的定义,这样才能明⽩后⾯的内容。

第⼀次操作⾸先,让我们来⼀步步模拟⼀下第⼀次操作。

我们第⼀步是要将每个后缀按照第1个字符进⾏排序。

这应该还是⽐较简单的,不难发现可以初始化得到rk i=s i,pos i=i。

然后我们对其进⾏第⼀次排序。

注意,排序最好⽤O(n)的基数排序,⽤sort的话会多⼀个log。

具体的⼀些关于基数排序的细节可以见下。

关于基数排序后缀排序中的基数排序,其实相当于将⼆元组(rk i,pos i)进⾏排序。

⾸先,第⼀步⾃然是清空tot数组。

加1。

然后,从1到n枚举,将tot rki接下来是⼀遍累加,求出每⼀个元素的排名。

然后从n到1倒序枚举,更新SA数组即可。

接下来的操作接下来⾃然是要对每个后缀前2个字符进⾏排序了。

暴⼒的⽅法就是再重新排序⼀遍。

但实际上,在确定了第1个字符的⼤⼩关系后,我们就不需要如此⿇烦了。

因为后缀i的第2个字符,实际上就是后缀i+k的第1个字符。

因此我们通过第⼀次排序,就可以直接确定第2个字符的⼤⼩关系了。

于是我们就可以重新⽤pos数组将这个⼤⼩关系记录下来,再次排序。

然后就是按照这种⽅法来倍增处理第4个字符、第8个字符、第16个字符... ...重复此操作直⾄所有后缀各不相同即可。

这样的总复杂度就是O(nlogn)的了。

具体实现还是有很多细节的,实在没理解的可以根据代码再研究⼀下。

后缀数组优化方法

后缀数组优化方法

后缀数组优化方法
4. 压缩后缀数组:在实际应用中,后缀数组可能会占用较大的内存空间。为了减少内存的 使用,可以采用压缩后缀数组的方法。具体而言,可以将后缀数组中的元素进行压缩,并使 用更少的空间来存储后缀的起始位置。
以上是一些常见的后缀数组优化方法。根据具体的问题和需求,可以选择适合的优化方法 来提高后缀数组的构建和使用效率。
后缀数组优化方法
后缀数组是一种用于解决字符串相关问题的数据结构,它可以高效地进行字符串匹配、最 长公共子串等操作。在构建后缀数组时,可以使用一些优化方法来提高算法的效率。以下是 一些常见的后缀数组优化方法:
1. 倍增法:倍增法是一种常用的构建后缀数组的方法。它通过不断扩大步长来进行迭代, 从而减少比较的次数。具体而言,倍增法首先将字符串中的字符按照ASCII码排序,然后根据Байду номын сангаас字符的排名构建后缀数组。在构建过程中,每次比较的步长会逐渐加倍,从而减少比较的次 数。
后缀数组优化方法
2. DC3算法:DC3算法是一种高效的后缀数组构建算法,它的时间复杂度为O(n),其中n 是字符串的长度。DC3算法的核心思想是将字符串划分为三个等长的子串,然后分别对这三 个子串构建后缀数组。通过递归地应用这个思想,最终可以得到完整的后缀数组。
3. Kasai算法:Kasai算法是一种用于构建后缀数组的优化方法,它可以在O(n)的时间复 杂度内计算出后缀数组中相邻后缀的最长公共前缀。这个最长公共前缀信息可以在后续的字 符串匹配和相关操作中发挥重要作用。

后缀数组——处理字符串的有力工具

后缀数组——处理字符串的有力工具
IOI2009 国家集训队论文
后缀数组 罗穗骞
信息学奥林匹克
China Nation Olympiad in Informatics
国家集训队论文
题 目: 后缀数组——处理字符串的有力工具
作 者:
罗穗骞
指导教师:
张学东
学 校:
华南师范大学附属中学
完成时间:
2009年1月
IOI2009 国家集训队论文
1.2倍增算法
倍增算法的主要思路是:用倍增的方法对每个字符开始的长度为 2k 的子字 符串进行排序,求出排名,即 rank 值。k 从 0 开始,每次加 1,当 2k 大于 n 以 后,每个字符开始的长度为 2k 的子字符串便相当于所有的后缀。并且这些子字 符串都一定已经比较出大小,即 rank 值中没有相同的值,那么此时的 rank 值就 是最后的结果。每一次排序都利用上次长度为 2k-1 的字符串的 rank 值,那么长 度为 2k 的字符串就可以用两个长度为 2k-1 的字符串的排名作为关键字表示,然 后进行基数排序,便得出了长度为 2k 的字符串的 rank 值。以字符串“aabaaaab” 为例,整个过程如图 2 所示。其中 x、y 是表示长度为 2k 的字符串的两个关键字 。
6
IOI2009 国家集训队论文
本页已使用福昕阅读器进行编辑。 福昕软件(C)2005-2009,版权所有, 仅供试用后。缀数组 罗穗骞
具体实现: int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; int cmp(int *r,int a,int b,int l) {return r[a]==r[b]&&r[a+l]==r[b+l];} void da(int *r,int *sa,int n,int m) { int i,j,p,*x=wa,*y=wb,*t; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[x[i]=r[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; for(j=1,p=1;p<n;j*=2,m=p) { for(p=0,i=n-j;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(i=0;i<n;i++) wv[i]=x[y[i]]; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[wv[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i]; for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } return; }

后缀数组及其应用

后缀数组及其应用

O(nlogn)
O(n)。
1、double_algorithm构造后缀数组;。。。。。O(nlogn) 2、线性计算出h[]数组,再逐个推出height[i];。。。O(n) 3、ST算法对height[]做预处理;。。。。。。。O(nlogn) 4、查询LCP(I,J)只需查询height[i…j]中的最小值 O(1)
关于RMQ问题(Range Minimum Query)
线段树等高级数据结构维护,O(nlogn)构造,O(logn) 查询,ST算法O(nlogn)构造,O(1)的查询。RMQ标准算法 O(n)构造,0(1)查询。
在信息竞赛中,均衡利弊还是ST实现简单,效率较高, 性价比高。 这样一来,我们可以在O(1)的时间内查询任意两个后缀的 最长公共前缀。这也是后缀数组最强大的功能之一。 貌似问题得到了解决,回顾刚才的过程,我们漏掉了一 个重要的过程——预处理排名相邻后缀 暴力枚举后缀再比较。时间显然超过了O(n)。我们希 望能在O(n)时间内解决这一问题
后缀数组的两种主流构造方法
倍增算法(Double Algorithm) O(nlogn)
三分算法(Difference Cover modulo 3 ) O(N)
倍增算法(Double Algorithm)
总体来说,倍增算法的思想与ST的思想差不多。将后缀 长度依次分为1,2,4,8,。。。,2^k进行排序。进行当 前排序时利用到上次的排序结果。
S=“BANANAS”。
BANANAS 的后缀树
BANANAS 的Trie
后缀树在处理字符串问题上有着得天独厚的空间优势和速 度优势,在最坏情况下, 后缀树的节点数也不会超过2N。主 流的构造方法是由Esko Ukkonen 于1995年发明的一种线 性构造法,理论时间复杂度为O(N)。非常优秀。

后缀数组 模板题

后缀数组 模板题

后缀数组模板题后缀数组是一种用于处理字符串的数组,其中每个元素都是原字符串的一个后缀。

在算法和数据结构中,后缀数组常用于解决字符串相关的问题,例如字符串匹配、最长重复子串等。

以下是一个使用后缀数组解决最长重复子串问题的模板题:题目描述:给定两个字符串s和p,找出s中最长也是p中的子串的长度。

如果不存在这样的子串,则返回0。

输入格式:输入的第一行包含一个字符串s,长度不超过100000。

输入的第二行包含一个字符串p,长度不超过100000。

输出格式:输出一个整数,表示s中最长也是p中的子串的长度。

示例:输入:abcdefgabcdfeg输出:4解释:在字符串s和p中,最长的公共子串是"efg",其长度为4。

算法思路:1. 构建后缀数组:首先将字符串s转换为后缀数组SA,可以使用KMP算法或Manacher算法。

2. 构建部分匹配数组PM:对于后缀数组SA中的每个元素,统计以该元素结尾的子串在p中出现的次数,保存在部分匹配数组PM中。

注意,PM的长度应为SA+1,因为我们需要包括空字符串。

3. 查找最长公共子串:遍历后缀数组SA中的每个元素,对于每个元素i,从PM[i]开始向前搜索,找到最长的公共子串的长度。

具体做法是,对于每个元素i,如果PM[i]不为0,则说明以i结尾的子串在p中出现过。

从PM[i]开始向前搜索,找到最长的公共子串的长度。

如果PM[i]为0,则说明以i结尾的子串在p中没有出现过,直接跳过该元素。

4. 返回结果:返回找到的最长公共子串的长度。

后缀数组 模版

后缀数组 模版

后缀数组模版
后缀数组模版,提升字符串处理效率的利器。

在计算机科学领域,字符串处理是一个非常重要的课题。

而后缀数组作为一种
重要的数据结构,被广泛应用于字符串处理中,能够大大提升字符串处理的效率。

后缀数组是一个存储字符串所有后缀的数组,它能够快速地解决很多与字符串
相关的问题,比如最长公共子串、最长回文子串等。

通过构建后缀数组,我们可以在O(nlogn)的时间复杂度内解决这些问题,而且在实际应用中,后缀数组的效率往往比其他数据结构要高。

在实际应用中,后缀数组被广泛应用于字符串匹配、模式识别、基因序列分析
等领域。

比如在搜索引擎中,后缀数组可以用来加速字符串的匹配,提高搜索效率;在基因序列分析中,后缀数组可以用来寻找重复的基因序列,帮助科学家们更好地理解基因的结构和功能。

除此之外,后缀数组还可以被用来构建后缀树、解决最长公共前缀等问题,可
以说是一个非常强大的工具。

总的来说,后缀数组作为一种重要的数据结构,在字符串处理中起着举足轻重
的作用。

它能够大大提高字符串处理的效率,应用范围广泛,是计算机科学领域中的一把利器。

希望未来能够有更多的研究者投入到后缀数组的研究中,为其在实际应用中发挥更大的作用做出贡献。

后缀数组 百度百科

后缀数组 百度百科

后缀数组开放分类:计算机、程序设计后缀数组在字符串处理当中,后缀树和后缀数组都是非常有力的工具,其中后缀树大家了解得比较多,关于后缀数组则很少见于国内的资料。

其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。

可以说,在信息学竞赛中后缀数组比后缀树要更为实用。

因此在本文中笔者想介绍一下后缀数组的基本概念、构造方法,以及配合后缀数组的最长公共前缀数组的构造方法,最后结合一些例子谈谈后缀数组的应用。

基本概念首先明确一些必要的定义:字符集一个字符集∑是一个建立了全序关系的集合,也就是说,∑中的任意两个不同的元素α和β都可以比较大小,要么α<β,要么β<α(也就是α>β)。

字符集∑中的元素称为字符。

字符串一个字符串S是将n个字符顺次排列形成的数组,n称为S的长度,表示为len(S)。

S的第i个字符表示为S。

子串字符串S的子串S[i..j],i≤j,表示S串中从i到j这一段,也就是顺次排列S,S[i+1],...,S[j]形成的字符串。

后缀后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。

字符串S的从i开头的后缀表示为Suffix(S,i),也就是Suffix(S,i)=S[i..len(S)]。

关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u和v,如果相等则令i加1,否则若u<v则认为u<v,u>v则认为u>v(也就是v<u),比较结束。

如果i>len(u)或者i>len(v)仍未比较出结果,那么若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。

从字符串的大小比较的定义来看,S的两个开头位置不同的后缀u和v进行比较的结果不可能是相等,因为u=v的必要条件len(u)=len(v)在这里不可能满足。

后缀数组的定义及实现

后缀数组的定义及实现

后缀数组的定义及实现1 后缀数组的定义1.1 后缀数组的相关概念(1) 文本串的后缀:对于符号表Σ上的长度为n的文本串T[1...n],文字串T的后缀是指从第i个字符开始到T的末尾所形成的子串T[i...n],1 ≤i ≤n。

(2) 子串:在长度为n的文字串T中,下标从i到j的连续的(j-i+1)个字符所组成的字符序列就是文字串T的一个长度为( j – i + 1)子串,其中1 ≤i ≤j ≤n。

(3) 后缀数组:后缀数组SA是T的所有后缀T0, T1, ... T n-1的一个字典序排序,即SA[i] = j表示后缀T j是字符串集合{T0, T1, ... T n-1}中第i个最小字符串。

排序后的后缀数组具有如下性质:T SA[0] < T SA[1] < …< T SA[n - 1]下图为后缀数组及其名次示例(T = acaaccg$)此图表示描述了了SA中所需要得到的SA[i]和Rank[i],其中Rank[i] = j表示T中的第i个后缀在所有后缀的字典排名为j。

SA[i] = j的两种表示理解方式:(1) 表示T j在字符串集合中排名为i (2) 表示排名为i 的后缀的Position。

1.2 后缀数组的相关数组解释为了理解后缀数组,我们需要理解以下两个数组的含义以及它们之间的相互关系:SA数组(Pos数组):SA数组是我们需要直接使用的数组,SA数组是FM-index的基础,后面的bwt正是在SA的基础上进行变化得到文本L。

在1.1中提到:SA[i] = j表示后缀T j是字符串集合{T0, T1, ... T n-1}中第i个最小字符串。

因此SA[i] = j的两种表示理解方式:(1)表示T j在字符串集合中排名(字典序排名)为i。

(2)表示排名为i的后缀的Position。

Rank数组:Rank数组是后缀数组的逆数组SA-1,即若SA[j] = i,则Rank[i] = j,其实质反映了T中的第i个后缀在所有后缀中的字典序排名为j。

后缀数组(SA)

后缀数组(SA)

后缀数组(SA)SA 的⽤处(经典题型):求LCP,LCS,本质不同⼦串数出现 xxx 次⼦串,字符串中不重叠地出现⾄少两次的最长串长度最长AA式⼦串,连续的若⼲相同⼦串(AABB式)⾸先说⼀下,本篇博客是本⼈在oi-wiki学习后缀数组是写下的笔记,更详细的证明等见后缀数组主要包含两个数组:sa[ ]和rk[ ]。

sa[i]表⽰所有后缀中字典序第i⼩的那个后缀。

rk[i]表⽰i后缀的字典序排名。

求后缀数组模板提交处:其中倍增求SA的代码如下(使⽤基数排序):#include <iostream>#include <algorithm>#include <cmath>#include <cstdio>#include <cstring>#include <string>#define N 2000010template<typename T> inline void read(T &x) {x = 0; char c = getchar(); bool flag = false;while (isdigit(c)) {if (c == '-') flag = true; c = getchar(); }while (isdigit(c)) {x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); }if (flag) x = -x;}using namespace std;char s[N];int sa[N], rk[N], oldrk[N], id[N], p, n, w, limi;int bin[N];int main() {scanf("%s", s + 1);n = strlen(s + 1);for (register int i = 1; i <= n; ++i) ++bin[rk[i] = s[i]];for (register int i = 1; i <= 300; ++i) bin[i] += bin[i - 1];for (register int i = n; i > 0; --i) sa[bin[rk[i]]--] = i;limi = 300;//注意是limi不是p,因为for⼀开始时不会执⾏limi = pfor (w = 1; w < n; w <<= 1, limi = p) {//处理idp = 0; //注意p = 0for (register int i = n; i > n - w; --i) id[++p] = i;//注意"i"for (register int i = 1; i <= n; ++i)if (sa[i] > w) id[++p] = sa[i] - w;//注意"sa[i] - w" 注意"sa[i]"//处理samemset(bin, 0, sizeof(bin));for (register int i = 1; i <= n; ++i) ++bin[rk[id[i]]];for (register int i = 1; i <= limi; ++i) bin[i] += bin[i - 1];for (register int i = n; i > 0; --i) sa[bin[rk[id[i]]]--] = id[i];//处理rk(去重)memcpy(oldrk, rk, sizeof(rk));p = 0; //注意p = 0for (register int i = 1; i <= n; ++i)//注意"i - 1" 注意"oldrk"rk[sa[i]] = (oldrk[sa[i]] == oldrk[sa[i - 1]] && oldrk[sa[i] + w] == oldrk[sa[i - 1] + w]) ? p : ++p; //注意 + wif (p == n) break;//⼩优化:如果已经排好序了,就不⽤再排了。

字符串--后缀数组的运用

字符串--后缀数组的运用

字符串--后缀数组的运⽤后缀数组的运⽤主要体现在三个数组的使⽤之中1.sa[i]=n;表⽰的是排名是第i名的是第n个后缀2.rank[n]=i;表⽰的是第n个后缀在排序中是排在第⼏位的,它和sa数组是相反的。

3.height[ ]表⽰的是相邻的两个后缀之间的最长的公共前缀。

这三种数组的运⽤是有很多种,先总结两种。

⼀,求出现⾄少k次的最长不重复⼦串这就是height[ ]最典型的运⽤。

⼀般的思路是⼆分总的字符串的长度,来寻找符合条件的height[ ]中的段落,⼀旦出现符合条件的段落就记录下来,同时输出就可以。

例题题意:给出n个字符串,要求你找出⾄少在(n)/2个字符串中出现过的最长字串。

思路;这是⼀道典型的使⽤height[ ]数组的题⽬,这⾥只需要将给出的字符串都合并起来,同时在每⼀个合并的地⽅加上⼀个从来都没有出现过的字符,避免在两个字符串相交的地⽅出现符合条件的字串。

同时在最后合并的最后加上⼀个没有出现过的最⼩的字符。

这是为了,在建⽴三个数组的时候更好操作。

所以这题我们只要对⼀个长度进⾏⼆分,然后在height[ ]数组中分段就好了。

那么我们选择什么长度呢,这⾥只要选择所有给出的串中最长的⼀个,显然这是很符合条件的。

那么下⾯就是代码#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>#include<string>#include<vector>#include<stack>#include<bitset>#include<cstdlib>#include<cmath>#include<set>#include<list>#include<deque>#include<map>#include<queue>using namespace std;typedef long long ll;const double PI = acos(-1.0);const double eps = 1e-6;const int mod =1000000;const int maxn =140110;char s[1100];int str[maxn];int Rank[maxn],height[maxn];int sa[maxn],t[maxn],t2[maxn],c[maxn];int belong[maxn],visit[200];void build_sa(int * s,int n,int m){int i,*x = t,*y = t2;for(i = 0;i < m;i++)c[i] = 0;for(i = 0;i < n;i++)c[ x[i] = s[i] ]++;for(i = 1;i < m;i++)c[i] += c[i-1];for(i = n-1;i >= 0;i--) sa[--c[x[i]]] = i;for(int k = 1;k <= n;k <<= 1){int p = 0;for(i = n - k;i < n;i++) y[p++] = i;for(i = 0;i < n;i++) if(sa[i] >= k) y[p++] = sa[i] - k;for(i = 0;i < m;i++) c[i] = 0;for(i = 0;i < n;i++) c[ x[y[i]] ]++;for(i = 0;i < m;i++) c[i] += c[i-1];for(i = n-1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];swap(x,y);p = 1; x[sa[0]] = 0;for(i = 1;i < n;i++)x[sa[i]] = y[sa[i-1]] == y[sa[i]] && y[sa[i-1] + k] == y[sa[i] + k] ? p-1:p++;if(p >= n) break;m = p;}}void calheight(int * s,int n){int i,j,k = 0;for(i = 0;i < n;i++)Rank[sa[i]] = i;for(i = 0;i < n;i++){if(k) k--;int j = sa[Rank[i]-1];while(s[i+k] == s[j+k]) k++;height[Rank[i]] = k;}}int Judge(int n,int len,int num){int i,j,k;int cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[0]]]){cnt++;visit[belong[sa[0]]]=1;}for(i=1;i<n;i++){if(height[i]<len){cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}else{if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}if(cnt>=num) return 1;}return 0;}void out(int n,int len,int num){int i,j,cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[0]]]){cnt++;}visit[belong[sa[0]]]=1;for(i=1;i<n;i++){if(height[i]<len){if(cnt>=num){for(j=sa[i-1];j<sa[i-1]+len;j++) printf("%c",str[j]-20);printf("\n");}cnt=0;memset(visit,0,sizeof(visit));visit[0]=1;if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}else{if(!visit[belong[sa[i]]]){cnt++;visit[belong[sa[i]]]=1;}}}if(cnt>=num){for(j=sa[n-1];j<sa[n-1]+len;j++)printf("%c",str[j]-20);printf("\n");}}int main(){int n;int flag=1;while(scanf("%d",&n)==1&&n){if(!flag) printf("\n");else flag=0;int i,j,k;int pos=0,cnt=1,left=0,right=0;memset(belong,0,sizeof(belong));for(i=1;i<=n;i++){scanf("%s",s);int num=strlen(s);right=max(right,num);for(j=0;j<num;j++){str[pos+j]=int(s[j])+20;belong[pos+j]=i;}str[pos+num]=cnt++;pos=pos+num+1;}str[pos]=0;build_sa(str,pos+1,150);calheight(str,pos+1);int max_x=0;while(left<=right){int mid=(left+right)>>1;if(Judge(pos+1,mid,n/2+1)){max_x=mid;left=mid+1;}else{right=mid-1;}}//cout<<max_x<<"....."<<endl;if(max_x==0){printf("?\n");}else{out(pos+1,max_x,n/2+1);}}return 0;}2.求出现k次或者是⾄少k次的最长可重叠⼦串这个问题和上⾯的问题就很类似,实际上差别就不会很⼤,上⾯的要求是不能重叠,⽽这⾥则是可以重叠的,那么反⽽这个类型是更加简单的。

字符串匹配(三)----后缀数组算法

字符串匹配(三)----后缀数组算法

字符串匹配(三)----后缀数组算法⼀、什么是后缀数组: 字符串后缀Suffix指的是从字符串的某个位置开始到其末尾的字符串⼦串。

后缀数组 Suffix Array(sa)指的是将某个字符串的所有后缀按字典序排序之后得到的数组,不过数组中不直接保存所有的后缀⼦串,只要记录后缀的起始下标就好了。

⽐如下⾯在下⾯这张图中,sa[8] = 7,表⽰在字典序中排第9的是起始下标为7的后缀⼦串,这⾥还有⼀个⽐较重要的数组rank,rank[i] : sa[i]在所有后缀中的排名,⽐如rk[5]=0,表⽰后缀下标为5的⼦串在后缀数组中排第0个; rank数组与sa数组互为逆运算,rk[sa[i]]=i; 现在假如我们已经求出来了后缀数组,然后直接对已经排好序的后缀数组进⾏⼆分查找,这样就能匹配成功了,下⾯贴出代码:import java.util.Arrays;public class SuffixArrayTest {public static void main(String[] args) {match(); // 得到结果是5}static void match(){String s = "ABABABABB";String p = "BABB";Suff[] sa = getSa(s); // 后缀数组int l = 0;int r = s.length()-1;// ⼆分查找,nlog(m)while(r>=l){int mid = l + ((r-l)>>1);// 居中的后缀Suff midSuff = sa[mid];String suffStr = midSuff.str;int compareRes;// 将后缀和模式串⽐较,O(n);if (suffStr.length()>=p.length()) {compareRes = suffStr.substring(0, p.length()).compareTo(p);}else {compareRes = pareTo(p);}// 相等了输出后缀的起始位置if(compareRes == 0){System.out.println(midSuff.index);break;}else if (compareRes<0) {l = mid + 1;}else {r = mid - 1;}}}/*** 直接对所有后缀排序,因为字符串的⽐较消耗O(N),所以整体为N²log(N)* @param src* @return*/public static Suff[] getSa(String src){int strLength = src.length();// sa 即SuffixArray,后缀数组// sa 是排名到下标的映射,即sa[i]=k说明排名为i的后缀是从k开始的Suff[] suffixArray = new Suff[strLength];for (int i = 0; i < strLength; i++) {String suffI = src.substring(i); //截取后缀suffixArray[i] = new Suff(suffI, i);}Arrays.sort(suffixArray); //依据Suff的⽐较规则进⾏排序return suffixArray;}static class Suff implements Comparable<Suff>{String str; //后缀内容int index; //后缀的起始下标public Suff(String str, int index) {super();this.str = str;this.index = index;}@Overridepublic int compareTo(Suff o2) {return pareTo(o2.str);}@Overridepublic String toString() {return "Suff{"+"str='"+str+"\'"+",index="+index+"}";}}}⼆、倍增法 上⾯求后缀数组的⽅式时间复杂度为n²log(n),⼀般来说,时间复杂度只要达到了n平⽅级别都要想办法降低,于是就有⼀种叫做倍增法的⽅法来求后缀数组,基本思想就是: 1、先将每个字符排序得到sa,rank数组, 2、然后给每个字符增添⼀个字符,这样就变成了两个字符,最后⼀个字符⽆法增添字符,就需要处理好边界问题。

后缀数组

后缀数组

1
aa 11 1 aab0 aa 1 13 3 b0 3
1
ab 12 2 ab00 ab 2 20 5 00 0
2
b0 20 3 b000 b0 3 30 7 00 0
至此,后缀数组就已经求出来了 SA[]={4,6,8,1,2,3,5,7}; 想一想: 为什么当所有名次都不相同时就不用再往下比了 呢? 接下来就是程序实现了
for(i=0;i<n;i++) wv[i]=x[y[i]];
/*以下几行,在第二关键字排好序的基础上,对第一关键字排序*/ for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[wv[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
代码!!!
void CalHeight(int *SA,int *height,int n) { int i,j,k=0; for(i=1;i<=n;i++) rank[sa[i]=i; for(i=0;i<n;i++) { if(k!=0) k=k-1; else k=0; j=sa[rank[i]-1]; //suffix(j)排在suffix(i)前一名 while(r[i+k]==r[j+k]) k++; height[rank[i]]=k; } }
• 例 1 :最长公共前缀
给定一个字符串,询问某两个后缀的最长公共前缀。 // 直接套用, ans=min( height[ i ] )+rmq k<i<=j

后缀树和后缀数组

后缀树和后缀数组

后缀树和后缀数组基本概念子串:字符串S的子串S[i..j],i?j,表示S串中从i到j这一段,也就是顺次排列S[i],S[i+1],...,S[j]形成的字符串。

字符集:一个字符集Σ是一个建立了全序关系的集合,也就是说,Σ中的任意两个不同的元素α和β都可以比较大小,要么α<β,要么β<α(也就是α>β)。

字符集Σ中的元素称为字符。

字符串:一个字符串S是将n个字符顺次排列形成的数组,n称为S的长度,表示为len(S)。

S的第i个字符表示为S[i]。

子串:字符串S的子串S[i..j],i?j,表示S串中从i到j这一段,也就是顺次排列S[i],S[i+1],...,S[j]形成的字符串。

后缀:后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。

字符串S 的从i开头的后缀表示为Suffix(i),也就是Suffix(i)=S[i..len(S)] 例如S = mississippi,那么它的所有后缀为:Suffix(1) = mississippi = SSuffix(2) = ississippiSuffix(3) = ssissippiSuffix(4) = sissippiSuffix(5) = issippiSuffix(6) = ssippiSuffix(7) = sippiSuffix(8) = ippiSuffix(9) = ppiSuffix(10) = piSuffix(11) = iSuffix(12) = (empty)不难发现,S的任意一个子串一定是某一个后缀的前缀。

字符串的大小比较:指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u[i]和v[i],如果u[i]=v[i]则令i加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v(也就是v<u),比较结束。

如果i>len(u)或者i>len(v)仍比较出结果,那么若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。

详细解析后缀数组(RMQ及LCP)

详细解析后缀数组(RMQ及LCP)
询问数组height中下标从 i+1 到 j 范围内所有元素的最小值
经典的RMQ (Range Minimum Query)问题!!!
➢线段树、排序树 —— O(nlogn)预处理 , O(logn)每次询问 ➢标准RMQ方法 —— O(n)预处理 , O(1)每次询问
后缀数组——辅助工具
采用一种“神奇的”方法,可以在O(n)时间内计算出height数组 采用标准RMQ方法在O(n)时间内进行预处理 之后就可以在常数时间内算出任何的LCP(i,j)
名次数组
后缀数组——构造方法
如何构造后缀数组?
把n个后缀当作n个字符串,按照普通的方法进行排序 —— O(n2)
低效的原因 —— 把后缀仅仅当作普通的、独立 的字符串,忽略了后缀之间存在的有机联系。
后缀数组——构造方法
u[1..k] ,len(u)≥k 对字符串u,定义uk =
u
,len(u)<k
LCP Theorem 对任何1≤i<j≤n LCP(i,j)=min{LCP(k-1,k) | i+1≤k≤j} 称j-i为LCP(i,j)的“跨度”,LCP Theorem意义为: 跨度大于1的LCP值可以表示成一段跨度等于1的LCP值的最小值
后缀数组——辅助工具
LCP(i,j)=3
4=LCP(i,i+1)
后缀是一种特殊的子串 从某个位置i开始到整个串的末尾结束 S的从i开头的后缀等价于S[i..len(S)]
后缀数组——定义和符号
约定一个字符集Σ 待处理的字符串约定为S,约定len(S)=n 规定S以字符“$”结尾,即S[n]=“$”
“$”小于Σ中所有的字符 除了S[n]=“$”之外,S的其他字符都属于Σ
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

符 串 r 的 从 第 i 个 字 符 开 始 的 后 缀 表 示 为 Suffix(i) , 也 就 是 Suffix(i)=r[i..len(r)]。
大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也 就是对于两个字符串 u、v,令 i 从 1 开始顺次比较 u[i]和 v[i],如果 u[i]=v[i]则令 i 加 1,否则若 u[i]<v[i]则认为 u<v,u[i]>v[i]则认为 u>v (也就是 v<u),比较结束。如果 i>len(u)或者 i>len(v)仍比较不出结果,那 么 若 len(u)<len(v) 则 认 为 u<v , 若 len(u)=len(v) 则 认 为 u=v , 若 len(u)>len(v)则 u>v。
待排序的字符串放在 r 数组中,从 r[0]到 r[n-1],长度为 n,且最大值小 于 m。为了函数操作的方便,约定除 r[n-1]外所有的 r[i]都大于 0, r[n-1]=0。 函数结束后,结果放在 sa 数组中,从 sa[0]到 sa[n-1]。
7
IOI2009 国家集训队论文
后缀数组 罗穗骞
例 1:最长公共前缀 ……………………………………………………17 2.2 单个字符串的相关问题 …………………………………………………17
2.2.1 重复子串 ………………………………………………………18 例 2:可重叠最长重复子串 ………………………………………18 例 3:不可重叠最长重复子串(pku1743)…………………………18 例 4:可重叠的最长重复子串(pku3261)…………………………19
后缀数组 罗穗骞
例 10:长度不小于 k 的公共子串的个数(pku3415) ……………23 2.4 多个字符串的相关问题 …………………………………………………23
例 11:不小于 k 个字符串中的最长子串(pku3294) ……………………24 例 12:每个字符串至少出现两次且不重叠的最长子串(spoj220)……24 例 13:出现或反转后出现在每个字符串中的最长子串(pku3294)……24 三、结束语 …………………………………………………………………………25 参考文献 ……………………………………………………………………………25 致谢 …………………………………………………………………………………25
函数的第一步,要对长度为 1 的字符串进行排序。一般来说,在字符串的题 目中,r 的最大值不会很大,所以这里使用了基数排序。如果 r 的最大值很大, 那么把这段代码改成快速排序。代码:
for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[x[i]=r[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; 这里 x 数组保存的值相当于是 rank 值。下面的操作只是用 x 数组来比较字 符的大小,所以没有必要求出当前真实的 rank 值。 接下来进行若干次基数排序,在实现的时候,这里有一个小优化。基数排序 要分两次,第一次是对第二关键字排序,第二次是对第一关键字排序。对第二关 键字排序的结果实际上可以利用上一次求得的 sa 直接算出,没有必要再算一次。 代码: for(p=0,i=n-j;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; 其中变量 j 是当前字符串的长度,数组 y 保存的是对第二关键字排序的结果 。 然后要对第一关键字进行排序,代码: for(i=0;i<n;i++) wv[i]=x[y[i]]; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[wv[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i]; 这样便求出了新的 sa 值。在求出 sa 后,下一步是计算 rank 值。这里要注 意的是,可能有多个字符串的 rank 值是相同的,所以必须比较两个字符串是否 完全相同,y 数组的值已经没有必要保存,为了节省空间,这里用 y 数组保存 rank 值。这里又有一个小优化,将 x 和 y 定义为指针类型,复制整个数组的操作可以 用交换指针的值代替,不必将数组中值一个一个的复制。代码: for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
6
IOI2009 国家集训队论文
后缀数组 罗穗骞
具体实现: int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; int cmp(int *r,int a,int b,int l) {return r[a]==r[b]&&r[a+l]==r[b+l];} void da(int *r,int *sa,int n,int m) { int i,j,p,*x=wa,*y=wb,*t; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[x[i]=r[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i; for(j=1,p=1;p<n;j*=2,m=p) { for(p=0,i=n-j;i<n;i++) y[p++]=i; for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j; for(i=0;i<n;i++) wv[i]=x[y[i]]; for(i=0;i<m;i++) ws[i]=0; for(i=0;i<n;i++) ws[wv[i]]++; for(i=1;i<m;i++) ws[i]+=ws[i-1]; for(i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i]; for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; } return; }
【关键字】
字符串 后缀 后缀数组 名次数组 基数排序
【正文】
一、后缀数组的实现
本节主要介绍后缀数组的两种实现方法:倍增算法和 DC3 算法,并对两种算 法进行了比较。可能有的读者会认为这两种算法难以理解,即使理解了也难以用 程序实现。本节针对这个问题,在介绍这两种算法的基础上,还给出了简洁高效 的代码。其中倍增算法只有 25 行,DC3 算法只有 40 行。
目录
后缀数组 罗穗骞
摘要 …………………………………………………………………………………4 关键字 ………………………………………………………………………………4 正文 …………………………………………………………………………………4 一、后缀数组的实现 …………………………………………………………………4
1.1 基本定义
子串:字符串 S 的子串 r[i..j],i≤j,表示 r 串中从 i 到 j 这 一 段 , 也就是顺次排列 r[i],r[i+1],...,r[j]形成的字符串。
后缀:后缀是指从某个位置 i 开始到整个串末尾结束的一个特殊子串。字
4
IOI2009 国家集训队论文
后缀数组 罗穗骞
IOI2009 国家集训队论文
后缀数组 罗穗骞
信息学奥林匹克
China Nation Olympiad in Informatics
国家集训队论文
题 目: 后缀数组——处理字符串的有力工具
作 者:
罗穗骞
指导教师:
张学东
学 校:
华南师范大学附属中学
完成时间:
2009年1月
IOI2009 国家集训队论文
从字符串的大小比较的定义来看,S 的两个开头位置不同的后缀 u 和 v 进 行比较的结果不可能是相等,因为 u=v 的必要条件 len(u)=len(v)在这里不可 能满足。
后缀数组:后缀数组 SA 是一个一维数组,它保存 1..n 的某个排列 SA[1], SA[2],……,SA[n],并且保证 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。 也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺 次放入 SA 中。
2.3 两个字符串的相关问题 …………………………………………………21 2.3.1 公共子串 ………………………………………………………22 例 9:最长公共子串(pku2774,ural1517) ………………………22 2.3.2 子串的个数 ……………………………………………………23
2
IOI2009 国家集训队论文
2.2.2 子串的个数 ……………………………………………………19 例 5:不相同的子串的个数(spoj694,spoj705)………………19
相关文档
最新文档