后缀数组

在字符串处理当中,后缀树和后缀数组都是非常有力的工具。其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。

鉴于后缀数组的实用性,以及它的的难度,我写了这篇博文,希望能够对读者有一定的帮助。

首先了解一下有关定义:

1.子串:字符串str的字串sub[i.......j] 表示在字符串str中从i到j的一段字符串,长度为n的字符串共有(n+1)*n/2个字串

             如str="abcde",  "a","ab","cde","abcde"等均是str的子串;

2.后缀suffix: suffix[ i ]表示在字符串str中从i开始到字符串结尾的字串,如str="abcde",suffix[2]="cde",suffix[4]="3"。

3.后缀数组sa: 后缀数组 SA 是一个一维数组,它保存 1..n 的某个排列 SA[1 ] ,SA[2] , …… , SA[n] ,并且保证

 suffix(SA[i]) <   Suffix(SA[i+1]) , 1 ≤ i<n 。也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头 位置顺次放入 SA 中。即:sa[i]=j 表示排名为i的后缀在str中是以j开始的后缀,假设sa[2]=3,str="abcde" ,则表示的是后缀"cde"在所有后缀中排第2.

4.排序数组Rank:Rank[i]表示suffix[i]在所有后缀里排列的名次。简单的说,后缀数组(sa)是 “ 排第几的是谁? ” ,名次数组(Rank)是 “ 你排第几?

5.height数组后缀数组中相邻两个后缀的最大公共前缀, height[i] 的值是 sa[i-1]和sa[i] 的公共前缀长度。

6.字符串的比较:字符串比较是按照字典序比较的


有关定义了解后,我们开始走进算法

求后缀数组最常用的方法是倍增算法,至于为什么叫倍增算法我们不必纠结。。。。


 建立后缀数组采用倍增的方式, 我们需要知道以下先验知识:

       其中kSuffix(j) ,表示以位置j 的后缀的长度为k的前缀

性质1.1 对k≥n,Suffix(i)<kSuffix(j) 等价于 Suffix(i)<Suffix(j)。
性质1.2 Suffix(i)=2kSuffix(j)等价于Suffix(i)=kSuffix(j) 且 Suffix(i+k)=kSuffix(j+k)。
性质1.3 Suffix(i)<2kSuffix(j) 等价于Suffix(i)<kSuffix(j) 或 (Suffix(i)=kSuffix(j) 且 Suffix(i+k)<kSuffix(j+k))。

整个倍增算法的思路为, 我要给所有的后缀排序,首先我给这个后缀中长度为1的前缀排序,然后我再按前缀长度1*2=2 排序,然后我再按前缀长度2*2=4排序。
根据以上性质,在使用 基数排序法时, 每一次给长度k的前缀排序时都利用到了按长度k/2的前缀排序的结果。

#include<stdio.h>  
#include<string.h>  
#include<algorithm>  
using namespace std;  
const int N = 50005;  
int sa[N],Rank[N],height[N],c[N];  
int n,m;  
char str[N];  
int cmp(int r[],int a,int b,int l){  
    return r[a]==r[b]&&r[a+l]==r[b+l];  
}  
//以下为倍增算法求后缀数组
void build_SA(int n){  
    int x[N],y[N];  
    //第一次基数排序   
    memset(c,0,sizeof(c));  
    for(int i=0;i<n;i++) c[x[i]=str[i]]++; //字符串的ASCII为数组下标 
    for(int i=1;i<m;i++) c[i]+=c[i-1];//求前缀和 
    for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;  
      
      
    // 倍增算法   
    for(int k=1;k<=n;k<<=1){  
        int p=0; //p是不同后缀的数量 
        for(int i=n-k;i<n;i++) y[p++]=i;  
        for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;  
          
        memset(c,0,sizeof(c));  
        for(int i=0;i<n;i++) c[x[y[i]]]++;  
        for(int i=1;i<m;i++) c[i]+=c[i-1];  
        for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];  
          
        swap(x,y);  
        p=1;  
        x[sa[0]]=0;  
        for(int i=1;i<n;i++)   
           x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?p-1:p++;  
        if(p>=n) break;  
        m=p;  
    }  
}  
void get_Rank(int n){  
    for(int i=1;i<=n;i++)  
       Rank[sa[i]]=i;  
}  
void get_height(int n){  
    for(int i=0,k=0;i<n;i++){  
        if(k) k--;  
        int j=sa[Rank[i]-1];  
        while(i+k<n&&j+k<n&&str[i+k]==str[j+k]) k++;  
        height[Rank[i]]=k;  
    }  
}  






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值