后缀数组的学习

学之前个人觉得还是应该看一下罗穗骞的后缀数组的国家集训队论文,虽然一开始很难看懂(反正我基本上是完全没看懂,现在距离我第一次看那篇文章也过去了1个多月,看了很多别人的论文,现在个人感觉也只是明白了一个大概),但是能理解一个大概

一个数组 s ,长度为len 那么我们总是用suffix(i) 表示从i开始一直到结尾结束,也即i的后缀

因为每个下标的后缀长度绝对不一致,那么每一个后缀都利用字典序排列,所得到的排名 是绝对不会存在一致性的

后缀数组 sa[i] 表示排名第i位的后缀的起始下标 rank[i] 表示第i位的后缀排多少名 很容易得到公式sa[rank[i]] = i 那么我们只要求出sa[],那么rank[]的求解就迎刃而解了

这里就讲讲自己对倍增算法的理解 很显然,sa[]数组的求解不能靠蛮力解决

可以这么考虑,对于字符串之间的大小比较,如果前面已经区分出了大小, 其实后面是没必要再进行比较的

我们可以不断每个下标处标记出当前所取长度时的排名 那么我们初始所取长度为1,而这里倍增算法的意思是不断将这个长度*2, 也就是前 l 的长度 和 后 l 的长度结合形成了一个新的 2*l 长度的字符

结合的时候有2个排名的标记,称之为第一关键字和第二关键字 根据这两个关键字排序后结合得到新的排名,再将此排名保存下来

倍增算法停止的位置就是在每个后缀下标的排名都不一致时结束,因为这时候 已经达到目的了,后面的比较是毫无意义的

 1 int wa[maxn] , wb[maxn] , wv[maxn] , ws[maxn];
 2
 3 //这里相当于是判断以a,b开头得到长度为2*l的部分,是不是同时第一关键字和第二关键字都相等
 4 //所以应该像论文中那样给末尾添加一个关键字0,防止数组越界,而且0也说明末尾为空,字符串最靠前
 5 int cmp(int *r , int a , int b , int l)
 6 {
 7     return r[a] == r[b] && r[a+l] == r[b+l];
 8 }
 9 /*
10 函数中m表示字符中所能表示的大小范围,如果均为字符,转化为ASCII码,m的最大范围就是128
11 但是如果数组中保存的是整数,就要另外取值
12 */
13 void da(int *r , int *sa , int n , int m)
14 {
15     int i,j,p,*x=wa,*y=wb,*t;
16     //接下来的就是普通的计数排序,初始化长度为1时的排名,不懂得可以百度去看看
17     for(i=0 ; i<m ; i++) ws[i]=0;
18     /*
19     这里x[]保存的各个后缀的rank值,值得思考的是初始时是可以直接用它当前的值代替,
20     因为初始长度为1,那么当前的值的大小可以作为相对排名,直接利用同样可以得到正确的
21     关键字的排序,就省略了将这些相对大小转化成1,2,3,4,...这样的排名的必要
22     */
23     for(i=0; i<n ; i++) ws[x[i]=r[i]]++;
24     for(i=0; i<m ; i++) ws[i]=ws[i-1]++;
25     //下方i递减可以让相等的数原先若是在前面就继续排前面
26     for(int i=n-1 ; i>=0 ; i--) sa[--ws[x[i]]]=i;
27
28     /*
29     接下来开始不断倍增长度 j , p统计当前所能达到的rank值
30     那么按前面所说,p达到n时就可以退出循环了
31     */
32     /*这里m=p表示这里在初始化结束后,其实相对值已经转化为绝对的大小比较了
33     那么数据范围就只在1~p之间
34     y[i]保存第二关键字排名第i位的在y[i]的下标处的后缀
35     */
36     for(j=1 , p=1 ; p<n ; j*=2,m=p)
37     {
38         /*这里从i=n-j开始,是因为从这个点开始它根本就没有长为j的
39         作为第二关键字的后缀,也就是说,总长为2*j,它的后j位为空
40         我们就直接可以理解为它是最小的,所以依次保存其名次*/
41         for(p=0 , i=n-j; i<n ; i++) y[p++]=i;
42         /*不存在后j位处理了以后,就可以按照之前得到的排名了,来安排第二关键字的排序
43         所以i表示原先的名次,不断去找到这个名次的后缀,保证其要长于j是因为这是后半部分,
44         它的前面还有第一关键字,必须保证前面还有j个位置给第一关键字的字符串才能表示这个
45         下标作为第二关键字是合法的
46         */
47         for(i=0 ; i<n ; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
48         //用一个数组暂时的保存第二关键字第i名的对应的第一关键字的值
49         for(i=0 ; i<n ; i++) wv[i]=x[y[i]];
50         //就是按计数排序的方法对第1关键字排序
51         for(i=0 ; i<m ; i++) ws[i]=0;
52         for(i=0 ; i<n ; i++) ws[wv[i]]++;
53         for(i=1 ; i<m ; i++) ws[i] += ws[i-1];
54         for(i=n-1 ; i>=0 ; i--) sa[--ws[wv[i]]]=y[i];
55
56         /*
57         t=x , x=y , y=t,这里利用指针的地址交换,避免了一个个的将数组中的
58         数据复制到另一个数组中,相当于只是通过简单的改了地址的名字实现了数组
59         间的数据交换
60
61         根据新得到的排名关系不断判断前后两名是不是其实2个关键字都是相同的
62         相同则当前设为p-1也就是和上一名的排名相同
63         否则排名增加一位
64         */
65         for(t=x , x=y , y=t , p=1 , x[sa[0]]=0 , i=1 ; i<n ; i++)
66             x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
67     }
68     return ;
69 }

后缀数组中只是利用一个sa[]数组是很难解决问题的 当然rank[]通过rank[sa[i]]=i得到也是不够的

还要引进height[] height[i] 表示 suffix(sa[i-1]), suffix(sa[i])的公共前缀长度,也就是排名相连 的两个后缀的最长公共前缀

这里需要引进一个定义存在下标j,k 假设rank[j]<rank[k] 那么suffix(j) , suffix(k)的公共前缀是 height[ran[j]+1] , height[ran[j]+2] ... , height[rank[k]]中的最小值 (好吧,我承认我还是不知道为什么) 那么这里高效的求出height[]显得至关重要

对于height[]数组的求解就利用一个h[]来理解 h[i]表示suffix(i) 和 suffix(sa[rank[i]-1])的公共前缀 其实可以看出h[i] = height[rank[i]]

h数组性质的证明: h[i] >= h[i-1]-1 假设i-1的前一名下标为k那么 h[i-1] 就是suffix(i-1),suffix(k)的公共前缀

1. 假设这个前缀存在 h[i-1] > 0 , 那么r[i-1]=r[k] , 而rank[k]>rank[i-1], 除去最前面的相同的数,那么rank[k+1]>rank[i] 依旧成立 那么suffix(k+1) 和 suffix(i) 的公共前缀就定义成 val = h[i-1]-1 但是我们不能保证 k+1 就是 i 的前一名,但是val = min(height[rank[i]+1] ... height[rank[k]])的 而h[i]存在于其中也就是height[rank[i]+1],那么必然大于等于val也就是h[i-1]-1的 所以第一种情况这个性质成立

2. 假设这个前缀不存在 h[i-1] = 0 , 而h[] 是个非负整数,h[i-1]-1=-1必然小于h[i] 所以第二种情况性质依然成立

我们就可以利用这个性质在线性的时间内求得height[]

 1 int rank[maxn] , height[maxn];
 2 void callheight(int *r , int *sa , int n)
 3 {
 4     int i , j , k=0;
 5     for(i=1;i<=n;i++) rank[sa[i]]=i;
 6     /*
 7     若k存在,就k--表示将第一位相同的数据抵消了,否则依然保持0
 8     然后从当前已知的能到达的最远的位置不断向后判断两个位置上对应的
 9     值是否相等,相等那么k就不断加1
10     */
11     for(i=0;i<n;height[rank[i++]]=k)
12         for(k?k-- , j=sa[rank[i]-1] ; r[i+k]==r[j+k] ; k++);
13
14     return;
15 }
时间: 2024-12-08 16:11:36

后缀数组的学习的相关文章

[后缀数组]【学习笔记】【未完】

研究了好长时间....(诶好像莫比乌斯反演时也说过这句话) 参考资料: 1.http://wenku.baidu.com/link?url=Beh6Asxvtm7M2QY5kiPyKKaP87xvBrNBKW9LXOeGKm-WM4GoUM3opnHZ8z-DahF7TRaLZZ4cpUe6jfFF064XUEmAiIDF7t90CpgNfSC3_Pq 2.http://www.cnblogs.com/staginner/archive/2012/02/02/2335600.html 3.htt

后缀数组总结

后缀数组总结 学习后缀数组可以参考2009年国家集训队论文<后缀数组--处理字符串的有力工具>BY罗穗骞 这里只有代码 const int N = 1e6+5; char s[N]; int n,a[N],t[N],x[N],y[N],SA[N],Rank[N],Height[N],ans; bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];} void getSA() { int m=30; for (

后缀数组学习笔记【详解|图】

后缀数组学习笔记[详解] 老天,一个后缀数组不知道看了多少天,最后终于还是看懂了啊! 最关键的就是一会儿下标表示排名,一会用数值表示排名绕死人了. 我不知道手跑了多少次才明白过来.其实我也建议初学者手跑几遍,但是一定要注意数组的意义,否则就是无用功. 数组含义: s[ ]:输入的字符串,预处理的时候会在末尾加上一个0 sa[ ]:它的下标就是后缀排名 x[ ] = t[ ]:用来保存第一关键字排名,注意!它的数值是排名.初始时恰好是字符串的ASCII码.字典序嘛! y[ ] = t2[ ]:它的

学习笔记----后缀数组

学习资料:IOI2009国家集训队论文--<后缀数组> 论文里面写的比较清晰了,但是代码里面没有解释,又从网上找到了一份代码的注释,解释的挺好的 地址:http://www.cnblogs.com/Lyush/p/3233573.html 这里是代码模板: 倍增算法实现的,效率很高. const int maxn = 10010; int wa[maxn], wb[maxn], wv[maxn], ws1[maxn]; int cmp(int *r, int a, int b, int l)

【算法学习】后缀数组

一个字符串的题,有姿势水平的OIers的脑中应该要浮现出许多算法-- 但是我没有姿势,也没有水平,除了KMP和trie树,什么也想不起来. 直到我学了它--后缀数组! 多亏这玩意儿,我现在什么都想不起来了. 后缀数组干嘛用的? 主要处理同一个字符串中的重复子串问题. 如何实现? 注意到每一个子串,都是一个后缀的某个前缀,这个后缀和前缀都是唯一确定的. 而后缀相同的前缀,和他们的字典序有密切联系.你有没有想过,字典中的相邻单词,他们的公共前缀总是很长. 一个字符串的任意后缀,都能用它的起始位置的下

算法学习:后缀数组 height的求取

[定义] [LCP]全名最长公共前缀,两个后缀之间的最长前缀,以下我们定义 lcp ( i , j ) 的意义是后缀 i 和 j 的最长前缀 [z函数] 函数z [ i ] 表示的是,第 i 个后缀和字符串的最长前缀  [解决问题] 这两个算法都是在解决这个问题 即求后缀和字符串和后缀之间的最长公共前缀 但是有所不同的是, 后缀数组最终求出的是,字典序第 i 个后缀和第 i + 1 个后缀的最长公共前缀 z函数最终求出的是,第 i 个后缀和字符串的最长公共前缀 然后通过这个最长公共前缀求一些其他

学习笔记:后缀数组

后缀数组是指对于后缀排序后,每个后缀的位置:sa[rank]=pos:排名为rank的后缀是pos->len这个后缀 note:rank[pos]=rank:位置为pos的串排名为rank 白书上的代码简洁明了,很容易理解. 核心思想:我们对于每个位置开始的后缀,不直接计算,先计算从这个位置开始,向后1位是第几小,然后向后2位,向后4位,一直到*2>n,这时就算好了后缀数组 复杂度:O(n*log(n)^2) :倍增log(n),快排log(n) 代码: bool cp(int x,int y

后缀数组 DC3构造法 —— 详解

学习了后缀数组,顺便把DC3算法也看了一下,传说中可以O(n)复杂度求出文本串的height,先比较一下倍增算法和DC3算法好辣. DC3 倍增法 时间复杂度 O(n)(但是常数很大)   O(nlogn)(常数较小) 空间复杂度   O(n)    O(n) 编程复杂度    较高   较低 由于在时间复杂度上DC3的常数比较大,再加上编程复杂度比较高,所以在解决问题的时候并不是最优选择.但是学到了后缀数组还是补充一下的好点. DC3算法的实现: 1:先把文本串的后缀串分成两部分,第一部分是后

Ural 1297 Palindrome(Manacher或者后缀数组+RMQ-ST)

1297. Palindrome Time limit: 1.0 second Memory limit: 64 MB The “U.S. Robots” HQ has just received a rather alarming anonymous letter. It states that the agent from the competing «Robots Unlimited» has infiltrated into “U.S. Robotics”. «U.S. Robots»