后缀数组
后缀数组
假设我们要求出给定字符串的所有后缀及其顺序,朴素的做法时间复杂度为\(O(n^2)\),但是善加利用后缀的性质,我们可以在\(O(nlog^2n)\)时间内解决这个问题
算法:
我们令\(sa[i]\)为字符串S中当前第\(i\)小的后缀的起始坐标,\(rank[i]\)为从\(i\)起始地后缀的名次
我们考虑倍增,初始我们令\(len=1\),求出所有坐标开始的长度为1的子串大小,这可以通过一次排序实现
现在我们假设我们已经求出了所有坐标开始的长度为\(K\)的子串顺序,那么当我们求长度为\(2K\)的子串顺序是,可以直接通过原来长度为\(K\)的子串rank为第一关键字,每个子串后接的另一个已求得长度为\(K\)的子串的rank为第二关键字,进行排序,这样总共倍增了\(logn\)次,每次复杂度为\(O(nlogn)\)
代码:
bool cmp(int x,int y) //通过比较两次rank比较子串大小
{
if (rank[x]!=rank[y])
return rank[x]<rank[y];
else
{
int rx,ry;
if ((x+len)>n) rx=-1; else rx=rank[x+len];
if ((y+len)>n) ry=-1; else ry=rank[y+len];
//注意子串长度不够置为最小值-1
return rx<ry;
}
}
void Suffix_Array()
{
rep(i,1,n) sa[i]=i,rank[i]=(int)(s[i]);
for (len=1;len<=n;len<<=1)
{
sort(sa+1,sa+n+1,cmp);
temp[sa[1]]=1;
rep(j,2,n)
if (cmp(sa[j-1],sa[j])) temp[sa[j]]=temp[sa[j-1]]+1; else temp[sa[j]]=temp[sa[j-1]]; //中间数组记录新的rank
rep(j,1,n) rank[j]=temp[j];
}
}
基数排序版本,复杂度为\(O(nlogn)\)
void Suffix_Array()
{
maxi=n;
rep(i,1,n) sa[i]=i,rank[i]=(int)(s[i]),maxi=max(rank[i],maxi);
for (len=1;len<=n;len<<=1)
{
memset(c,0,sizeof(c));
rep(i,1,n) //按第二关键字排序
{
k=sa[i];
if ((k+len)>n) next[k]=0; else next[k]=rank[k+len];
c[next[k]]++;
}
rep(i,1,maxi) c[i]+=c[i-1];
dep(i,n,1) temp[c[next[sa[i]]]--]=sa[i];
memset(c,0,sizeof(c)); //按第一关键字排序
rep(i,1,n) c[rank[temp[i]]]++;
rep(i,1,maxi) c[i]+=c[i-1];
dep(i,n,1) sa[c[rank[temp[i]]]--]=temp[i];
temp[sa[1]]=1;
rep(i,2,n)
{
temp[sa[i]]=temp[sa[i-1]];
if (!((rank[sa[i]]==rank[sa[i-1]])&&(next[sa[i]]==next[sa[i-1]]))) temp[sa[i]]++;
}
rep(i,1,n) rank[i]=temp[i];
maxi=rank[sa[n]];
if (maxi>=n) break;
}
}
查找字符串:
假设我们已经求得模板串T的SA数组,那么我们在串T中查找S时只需在所有后缀中二分即可,复杂度为\(O(|S|log|T|)\),当S长度较小且多次匹配时,该算法比KMP优
int compare(int k)
{
rep(i,k,k+nt-1)
{
if (s[i]<t[i-k+1]) return 1;
if (s[i]>t[i-k+1]) return 0;
}
return -1;
}
int Sa_match()
{
l=1,r=n;
while (l<(r-1))
{
mid=(l+r)>>1;
if (compare(sa[mid])==1) l=mid+1; else r=mid;
}
if (compare(sa[l])==-1)
return sa[l];
else
{
if (compare(sa[r])==-1)
return sa[r];
else
return -1;
}
}
高度数组:
我们令\(Lcp[i]\)为SA数组中第\(i\)项和第\(i+1\)项的最长公共前缀长度,可以证明当我们按原数组顺序递推时,Lcp的值最多减1(全部去掉首字母),所以可以在\(O(n)\)时间内求得整个Lcp数组,则任意两个位置的LCP即为他们在SA数组中的区间最小值,即RMQ问题
void LCP_Array()
{
rep(i,1,n) rank[sa[i]]=i;
h=0;
rep(i,1,n)
{
j=sa[rank[i]-1];
if (h>0) h--;
for (;((i+h)<=n)&&((j+h)<=n);h++)
if (s[i+h]!=s[j+h]) break;
lcp[rank[j]]=h;
}
}

浙公网安备 33010602011771号