【模板】后缀数组(含代码解析)

说说我的看法吧。

1.sa数组和rnk数组就是一个互逆,原字符串的后缀子串的排名。

2、求sa数组用的是倍增,也就是我一开始不是直接求后缀的,我是求长度为k的子串的排名,在推到k+1的排名的,具体用到基数排序来做。

3.得出来的rnk是从0开始的,注意这一点。然后基数排序时注意有个倒序,具体原因就是旧的sa数组是之前排第二关键字来的,所以得sa数组大的先排,这个yy一下就好了。。

4、然后在求sa的过程中,m(也就是桶的大小是可以优化一下的)。

5.height数组我这里写得是h,h[i]代表着排名为i的后缀子串和排名i-1的后缀子串的最长公共前缀,而又由于这个数组有个性质可以O(n)跑出来。

6、排名相邻的后缀子串相似度最高,也就是公共前缀最长

7、排名i的后缀子串和排名j的后缀子串的最长公共子串等于 h[i+1]到h[j]的最小值,我们可以这样看,i+1的相当于i的串演变过来,(因为相似度最高),然后一步步到j,所以是h的最小值

总复杂度nlogn,因为是倍增log,基数排序O(n)

模板是用我们集训队一位大佬的。不过感觉好像这个板子网上也好常见,,,不过好用就行

下面是无注释的

 1 void get_sa(int m){
 2     int n=strlen(s);
 3     int *x = t, *y = t2;
 4     for(int i = 0; i < m; i++) c[i] = 0;
 5     for(int i = 0; i < n; i++) c[x[i] = s[i]]++;
 6     for(int i = 1; i < m; i++) c[i] += c[i - 1];
 7     for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
 8     for(int k = 1; k <= n; k <<= 1) {
 9         int p = 0;
10         for(int i = n - k; i < n; i++) y[p++] = i;
11         for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;
12         for(int i = 0; i < m; i++) c[i] = 0;
13         for(int i = 0; i < n; i++) c[x[y[i]]]++;
14         for(int i = 1; i < m; i++) c[i] += c[i - 1];
15         for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
16         swap(x, y);
17         p = 1; x[sa[0]] = 0;
18         for(int i = 1; i < n; i++)
19             x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
20         if(p >= n) break;
21         m = p;
22     }
23 }
24 void get_h(){
25     int n=strlen(s);
26     int k = 0;
27     for(int i = 0; i < n; i++) rnk[sa[i]] = i;
28     for(int i = 0; i < n; i++) {
29         if(k) k--;
30         int j = sa[rnk[i] - 1];
31         while(s[i + k] == s[j + k]) k++;
32         h[rnk[i]] = k;
33     }
34 }
View Code

 下面是加了点注释的

 1 void get_sa(int m){
 2     int n=strlen(s);
 3     int *x = t, *y = t2;   //两个临时数组,x是临时的rnk数组,y是临时的sa数组 
 4     for(int i = 0; i < m; i++) c[i] = 0;
 5     for(int i = 0; i < n; i++) c[x[i] = s[i]]++;  //一开始x(rnk)数组初始化为s[i],s是字符串 
 6     for(int i = 1; i < m; i++) c[i] += c[i - 1];
 7     for(int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
 8     for(int k = 1; k <= n; k <<= 1) {
 9         int p = 0;
10         //按照i+k的rnk来排sa数组,这里用y暂存sa,也就是谁的i+k的旧rnk小,sa数组就排名靠前 
11         for(int i = n - k; i < n; i++) y[p++] = i;   //没有后面k位的,自然是最大,这里y是临时的sa 
12         for(int i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i] - k;//再排有后面k位的。按照后k位的排名作关键字 
13         //按照i的rnk来排序  
14         for(int i = 0; i < m; i++) c[i] = 0;//清空桶 
15         for(int i = 0; i < n; i++) c[x[y[i]]]++; //x是旧的rnk,y是刚刚排完第二关键字的sa数组 
16         for(int i = 1; i < m; i++) c[i] += c[i - 1];
17         for(int i = n - 1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i]; //更新sa数组,注意要倒序,因为y是排了第二关键字的,所以第二关键字排的后的,现在应该排得后些,也就是先选 
18         //然后现在已经更新了sa数组,此时y数组没用了,但是rnk没更新,x是旧的rnk,所以我们把x换给y,用旧的rnk(现在是y了)来更新新的rnk(现在是x) 
19         swap(x, y);
20         p = 1; x[sa[0]] = 0;//x是新rnk! 
21         for(int i = 1; i < n; i++)
22             x[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? p - 1 : p++;
23         if(p >= n) break; //没有一样的rnk就可以退了,因为第一关键字已经没有一样的了,接下来再比第二关键字也没用 
24         m = p;  //更新桶的大小,因为桶的大小是视rnk最大那个而定 
25     }
26 }
27 //这里h数组实际是网上的height数组,也就是下标应该是排名而不是字符串下标 
28 void get_h(){
29     int n=strlen(s);
30     int k = 0;
31     for(int i = 0; i < n; i++) rnk[sa[i]] = i;  //求逆 
32     for(int i = 0; i < n; i++) {
33         if(k) k--;            //因为height数组的性质 
34         int j = sa[rnk[i] - 1];
35         while(s[i + k] == s[j + k]) k++;  //至少前k个相同,所以不用比较 
36         h[rnk[i]] = k;
37     }
38 }
View Code

附上kuangbin的模板吧

这份模板是sa数组是1到n的,然后rnk数组是从0到n-1的,也就是排名是从1开始排名的。

因为感觉之前的模板有锅,因为排名为0的后缀是有意义的,但是询问到他上面就会RE了。这份模板的话先是++n,其余的都一样,那么也就是末尾'\0'会被计算,

放到sa【0】那里,所以就使得sa从1开始是有意义的。这样就不会RE了。kuangbin nb!

 1 void get_sa(int m){
 2     int n=strlen(s);
 3     ++n;
 4     int* x=t,*y=t2;
 5     for(int i=0;i<m;++i) c[i]=0;
 6     for(int i=0;i<n;++i) c[x[i]=s[i]]++;
 7     for(int i=1;i<m;++i) c[i]+=c[i-1];
 8     for(int i=n-1;i>=0;--i) sa[--c[x[i]]]=i;
 9     for(int k=1;k<=n;k<<=1){
10         int p=0;
11         for(int i=n-k;i<n;++i) y[p++]=i;
12         for(int i=0;i<n;++i) if(sa[i]>=k) y[p++]=sa[i]-k;
13         for(int i=0;i<m;++i) c[i]=0;
14         for(int i=0;i<n;++i) c[x[y[i]]]++;
15         for(int i=1;i<m;++i) c[i]+=c[i-1];
16         for(int i=n-1;i>=0;--i) sa[--c[x[y[i]]]]=y[i];
17         swap(x,y);
18         p=1;x[sa[0]]=0;
19         for(int i=1;i<n;++i){
20             x[sa[i]]=(y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k])?p-1:p++;
21         }
22         if(p>=n) break;
23         m=p;
24     }
25     int k=0;
26     n--;
27     for(int i=0;i<=n;++i) rnk[sa[i]]=i;
28     for(int i=0;i<n;++i){
29         if(k) k--;
30         int j=sa[rnk[i]-1];
31         while(s[i+k]==s[j+k]) k++;
32         h[rnk[i]]=k;
33     }
34 }
View Code

 

转载于:https://www.cnblogs.com/xiaobuxie/p/11325945.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值