后缀数组

后缀数组

设S是一个字符串,其长度为length(S),在S中第i个字符是S[i],S[i…j]是在S中从S[i]到S[j]的子串,1≤i≤j≤length(S)。

S的后缀数组的元素是从第i个字符开始的后缀,1≤i≤length(S),表示为Suffix(S, i),即,Suffix(S, i)=S[i…length(S)]。

为了叙述的方便,对于字符串S,从第i个字符开始的后缀记为Suffix(i)。

在一个字符串的后缀数组中,该字符串的所有后缀按字典序排序。对于一个长度为n的字符串,有n个不同的后缀。后缀数组SA和名次数组Rank用来表示n个后缀的排序。

后缀数组SA:SA是一个存储了1, 2, …, n的一个排列的整数数组,Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n。字符串S的n个后缀按字典序排序,SA[i]存储第i个后缀的开始位置,即后缀数组SA表示按字典序排列后是第i个后缀是谁。

名次数组Rank: Rank是一个与SA对应的整数数组:如果SA[i]=j,则Rank[j]=i。Rank表示一个后缀所在的位置,即后缀数组SA是计算名次数组Rank的逆运算,Rank=SA-1

对于一个长度为n的字符串来说,如果直接比较任意两个后缀的大小,最多需要比较字符n-1次和后缀长度1次。也就是说,花O(n)时间一定能分出“大小”了。如果有名次数组Rank,仅用O(1)的时间就能比较出任意两个后缀的大小了。由于名次数组Rank与后缀数组SA互逆,因此可以在求出名次数组Rank后,直接通过SA[Rank[i]]=i(1≤i≤n)计算后缀数组SA[]。

以字符串“aabaaaab”为例

k=0,对每个字符开始的长度为20=1的子串进行排序,得到 Rank[1…8]={1, 1, 2, 1, 1, 1, 1, 2}。
k=1,对每个字符开始的长度为21=2的子串进行排序:用两个长度为1的字串的排名xy作为关键字xy[1…8]={11, 12, 21, 11, 11, 11, 12, 20},得到Rank[1…8]={1, 2, 4, 1, 1, 1, 2, 3}。
k=2,对每个字符开始的长度为22=4的子串进行排序:关键字xy[1…8]={14,21,41,11,12,13,20,30},得到Rank[1…8]={4, 6, 8, 1, 2, 3, 5, 7}。
k=3,对每个字符开始的长度为23=8的子串进行排序:关键字xy[1…8]={42,63,85,17,20,30,50,70},得到最后结果Rank[1…8]={4, 6, 8, 1, 2, 3, 5, 7}。
在这里插入图片描述

时间复杂度

计算一个字符串S的后缀数组SA:O(nlog2n)
利用SA可以进行进行模式匹配:O(m
log2n)
m,n分别为模式串和待匹配串的长度。

优点

基于名次数组Rank[]和最长公共前缀数组height[],可避免“蛮力”搜索,以简化和优化算法;
计算名次数组Rank[]和height[]的时空效率较高,且基本上都是有标准的程序段实现的;
因此,许多字串处理都将计算Rank[]和height[]作为核心子算法。

相对原文有修改,套用模板时r[]类型可能需要修改。

代码
#include <cstdio>
#include <iostream>
#include <cstring>
#define  LL long long
#define  ULL unsigned long long
using namespace std;
const int MAXN=100010;
//以下为倍增算法求后缀数组 
int wa[MAXN],wb[MAXN],wv[MAXN],Ws[MAXN];
int sa[MAXN],Rank[MAXN],height[MAXN];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
/**< 传入参数:str,sa,len+1,ASCII_MAX+1 */ 
/**
r[]:原始数据j当前字符串的长度,每次循环根据2个j长度的字符串的排名求得2j长度字符串的排名。
y[]:指示长度为2j的字符串的第二关键字的排序结果,通过存储2j长字符串的第一关键字的下标进行指示。
wv[]:2j长字符串的第一关键字的排名序号。
ws[]:计数数组,计数排序用到。
x[]:一开始是原始数据r的拷贝(其实也表示长度为1的字符串的排名),之后表示2j长度字符串的排名。
p:不同排名的个数。
*/
void da(char r[],int n,int m)
{
      int i,j,p,*x=wa,*y=wb,*t; 
      for(i=0; i<m; i++) Ws[i]=0;
      for(i=0; i<n; i++) Ws[x[i]=r[i]]++;//以字符的ascii码为下标 
      for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
      for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i;
      /*cout<<"SA"<<endl;;
      for(int i=0;i<n+1;i++)cout<<sa[i]<<' ';*/
      for(j=1,p=1; p<n; j*=2,m=p)
      {
            for(p=0,i=n-j; i<n; i++) y[p++]=i; 
            for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
            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++;
      }
      return;
}
//求height数组
/**< str,sa,len */
void calheight(char *r,int n)
{
      int i,j,k=0;
      for(i=1; i<=n; i++) Rank[sa[i]]=i;
      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++);
      // Unified
      for(int i=n;i>=1;--i) ++sa[i],Rank[i]=Rank[i-1];
}
char str[MAXN];
int main()
{
      while(scanf("%s",str)!=EOF)
      {
            int len=strlen(str);
            da(str,len+1,130);
            calheight(str,len);
            puts("--------------All Suffix--------------");
            for(int i=1; i<=len; ++i)
            {
                  printf("%d:\t",i);
                  for(int j=i-1; j<len; ++j)
                        printf("%c",str[j]);
                  puts("");
            }
            puts("");
            puts("-------------After sort---------------");
            for(int i=1; i<=len; ++i)
            {
                  printf("sa[%2d ] = %2d\t",i,sa[i]);
                  for(int j=sa[i]-1; j<len; ++j)
                        printf("%c",str[j]);
                  puts("");
            }
            puts("");
            puts("---------------Height-----------------");
            for(int i=1; i<=len; ++i)
                  printf("height[%2d ]=%2d \n",i,height[i]);
            puts("");
            puts("----------------Rank------------------");
            for(int i=1; i<=len; ++i)
                  printf("Rank[%2d ] = %2d\n",i,Rank[i]);
            puts("------------------END-----------------");
      }
      return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值