后缀数组

后缀数组

假设我们要求出给定字符串的所有后缀及其顺序,朴素的做法时间复杂度为\(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;
 }
}
posted @ 2016-11-30 19:39  Krew  阅读(131)  评论(0)    收藏  举报