后缀数组

今天学习了后缀数组,有一些心得希望能和大家一起分享!

(1)后缀数组的定义:

假设有字符串S[0 ..... N-1],并且规定S串中最大的字符不超过M,即M=max{ str[i]| 0<=i<N };并且我们在字符串S的最后加上一个比所有S[i]( 0<=i<N )都小的值:S[N] = 0(假设S串中所有的字符都大于0)。以上这些都是运用后缀数组解题是S串的“规定”。下面,对于每个i,我们把substr(S , i , N)定义为S的一个后缀,那个整个字符串就会有N+1个后缀(这是因为我们在S的最后加上了一个最下的字符,所以是N+1个后缀)。我们定义一个数组SA[0 .. N],数组SA的值是对0...N的一个排列,对于任意的i < j ,都满足: strcmp( substr( S , SA[i] , N ), substr( S , SA[i] , N ) ) < 0 。 定义 rank[0 .... N],rank[ sa[i] ] = i。从以上的定义就可以看出,SA数组和rank数组其实是互逆的关系,SA[i]:表示的是第i小的后缀是哪个; rank[i]:表示的是第i个后缀第几小。

在运用后缀数组解题的时候,求出以上SA数组和rank数组是一个基础,一般题目应用是在height数组,height数组的定义如下所示:height[i] = substr(S , SA[i-1] , N ) 和 substr(S , SA[i] , N )的最长公共前缀长度。直接求解height数组最坏情况的复杂度为O(N^2),为了降低复杂度,我们引入了h数组,h数组的定义为:h[i] = height[ rank[i] ],h数组有以下性质:h[i] >= h[i-1] - 1 ,证明过程请看罗穗骞的《后缀数组——处理字符串的有力工具》论文。有了辅助数组h,我们就可以在O(N)的时间内求出height数组。

(2)倍增算法的实现

int r[NN] , sa[NN] ;
int wa[NN] , wb[NN] , wv[NN] , ws[NN] ;
int cmp(int *r, int a,int b , int l)
{return (r[a]==r[b]) && (r[a+l]==r[b+l] ) ;}
/*
倍增算法:对于N个后缀,首先对其首个key进行计数排序,然后对前2个key进行排序;
然后对其前4个key进行排序,接着是8个、16个。因为每次都进行一次基数排序,所以
每次排序的复杂度都为O(N),因为要进行O(logN)次排序,所以整个复杂度就是O(NlogN)
*/
void da(int *r,int *sa, int n , int m){
    int i, j ,*x=wa ,*y=wb ,*t ,p ;
    /*先对前1个key进行一次基数排序*/
    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 ;
    /*从后往前求出每个key的编号,也就是排序号,这里i的值从大到小是为了保证
    两个key相同时保持原来他们的先后顺序*/
    for(j=1,p=1;p<n;m=p,j<<=1){
        /*表示进行前j*2个key的排序*/
        /*下面进行两次基数排序,第一次是对前j*2个key的后j个key进行排序,第二次是对前j*2个key的前j个key进行排序*/
        for(i=n-j,p=0;i<n;i++)  y[p++] = i ;    
        /*对于从这些i位置开始的2*j个key的后j个key都是不存在的,因此它们是最小的,至于顺序是无所谓的*/
        for(i=0;i<n;i++)    if( sa[i] >= j )    y[p++] = sa[i] - j ;
        /*对于 sa[i] >= j 的后缀,它对应的2*j个key的前j个key是存在的,所以它要记在前j个key排序的数组中*/
        
        /*至此,y表示的就是以前2*j个key的后j个key排序的结果,y[i]表示排在第i个的后缀*/
        /*以下将要进行一次以前j个关键字进行排序的过程*/
        for(i=0;i<n;i++)    wv[i] = x[ y[i] ] ;     /*wv数组记录的是前j个key自身的值*/
        for(i=0;i<m;i++)    ws[i] = 0 ;            /*清空记录key出现次数的数组*/
        for(i=0;i<n;i++)    ws[ wv[i] ]++ ;         /*统计每个key出现的次数*/
        for(i=1;i<m;i++)    ws[i] += ws[i-1] ;      /*递推出小于各个key的个数*/
        for(i=n-1;i>=0;i--)     sa[--ws[wv[i]]] = y[i] ;  /*求出相应的sa数组,原理和上面类似*/
        
        /*接着是求解x数组,也就是rank数组,这里说明一下为什么不直接用 rank[ sa[i] ] =  i
        求解rank数组。这是因为此时的sa数组仅是前2*j个key排序的结果,虽然它是0..N的一个排列,
        但是此时的suf( sa[i-1] )和suf( sa[i] )有可能是相同的,假设我们给他们赋了不同的值,也就
        意味着在下面基数排序中,他们就不会被认为相等了,因为在下面进行的基数排序中,rank的值就
        代表了后缀的大小。综上我们这里不能用以上的公式直接去求rank,要增加一个判断suf( sa[i-1] )
        和suf( sa[i] )是否相等的过程*/
        for(t=x,x=y,y=t,i=1,x[sa[0]]=0,p=1;i<n;i++)
            x[sa[i]] = cmp(y , sa[i-1] , sa[i] , j)?p-1:p++ ;
    }
}
int rank[ NN ] , height[NN] ;
void calc_height(int *r, int *sa, int n){
    int i, j , k = 0 ;
    /*这里不先从0开始,是因为我们在S的最后加上了一个比所有后缀都小的后缀,因此sa[0]=N*/
    for(i=1;i<=n;i++)   rank[ sa[i] ] = i ;
    /*这里的i要小于n,也就是所,height[ rank[N] ]的值我们不需要计算,这是因为rank[N]=0
    height[0]是没有意义的。如果去计算height[0]就会导致一个RE,因为下面的for循环中有一个
    sa[ rank[i] - 1 ]*/
    for(i=0;i<n;height[rank[i++]]=k){
        for(k?k--:0,j=sa[rank[i]-1] ; r[i+k]==r[j+k];k++) ;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值