后缀数组学习

后缀数组

简述

后缀数组sa是一个一维数组,它是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入 SA 中。比如:BANANA的所有后缀是BANANA,ANANA,NANA,ANA,NA,A,按照字典序排列就是A,ANA,ANANA,BANANA,NA,NANA,那么后缀数组就是{5, 3, 1,0,4,2 }。

倍增算法

首先将单个字符排序,计算出每个字母的名词。

然后给所有后缀的前两个字符排序,这等价与给一些二元组排序,其中每个二元组就是一个后缀的前两个字符的名次。

接下来,是给每个后缀的前四个字符排序,注意,这次排序的对象仍然是二元组,注意到每个后缀k的前四个字符是由后缀k的前两个字符和后缀k+2的前两个字符组成,如果后缀k与k`相比,那么首先应该比较它们的前两个字符。

当所有名次已经两两不同,算法结束。

注意到字符种类最多m种,所以可以采用基数排序,算法每一轮的时间复杂度降为O(n),总的时间复杂度为O(nlogn)。

求解sa[]代码

void build_sa(int *r, int n, int m){                                
        int i,j,p,*x = wa, *y = wb, *t;  
        for(i = 0; i < m; i ++) ws[i] = 0;//ws是基数排序中统计数量的辅助数组  
        for(i = 0; i < n; i ++) ws[x[i] = r[i]] ++;  //统计各个桶中有多少个元素
        for(i = 1; i < m; i ++) ws[i] += ws[i-1];  //ws[i]表示第i个桶右边的索引
        for(i = n-1; i >= 0; i --) sa[--ws[x[i]]] = i;  //每放一个元素ws[i]减少1个
        for(j = 1, p = 1; p < n; j*=2, m = p){ //j代表当前字符串长度
            for(p = 0, i = n-j; i < n; i ++) y[p++] = i;//后j个字符第二关键字是最小的  
            for(i = 0; i < n; i ++)
            	if(sa[i] >= j)
            		y[p++] = sa[i]-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];
              
            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++;  //如果两个后缀前j个字符相等名次与上一个后缀相同,否则排在其后,同时p可代表不同字符串的数量
                                                                                            //p >= n代表不需要继续倍增,每一轮,字符范围不会超过p,可将m更新为p
            }  
    }  
int cmp(int *r,int a,int b,int l)  {  
        return r[a] == r[b] && r[a+l] == r[b+l];  
    }  

求解height[]数组

只有sa数组可以做的事情并不多,我们还需要两个辅助数组rank[],height[]。rank[i]代表后缀i在sa数组中的下标。height[i]代表sa[i-1]和sa[i]的最长公共前缀长度。对于两个后缀j,k,不难证明j和k的LCP长度等于height[rank[j]+1],height[rank[j]+2],......,height[rank[k]]中的最小值,即RMQ(height, rank[j]+1, rank[k])。O(n)时间计算height需要一个辅助数组h[i] = height[rank[i]],然后递推。递推的关键是:h[i] >= h[i-1]-1。

void calheight(int *r, int n){  
        int i, j, k=0;  
        for(i=1; i<=n; i++)  
        	rank[sa[i]] = i;        
        for(int i=0; i<n; i++){
        	if(k) k--;
        	int j = sa[rank[i]-1];
        	while(r[i+k] == r[j+k]) k++;
        	height[rank[i]] = k;
        }   
    }  



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值