后缀数组
设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(mlog2n)
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;
}