后缀数组,高度数组

后缀数组:SA[i]表示字典序第i的后缀的开头字母的位置,比如abca,后缀有五个,abca,bca,ca,a,空后缀

求后缀数组的方法是……类似分治法,类似二分法?

首先求出每个后缀首字母的顺序,然后依据这个顺序求出每个后缀首两个字母开头的后缀的顺序,最后就能求出每个后缀的顺序

具体实现上,参考挑战程序设计竞赛里给的(有更好的基数排序算法)(代码没编译过……下同)

int n,k;
int rank[maxn_n + 1];
int tmp[manx_n + 1];
for(int i = 0; i <= n; i++){
sa[i] = i;
rank[i] = i < n? s[i]:-1;//直接去字符值,空的是0
}
for(k = 1; k <= n; k*=2)
{
    sort(sa,sa + n + 1,compare_sa    tmp[sa[0]] = 0;

   for(int i = 1; i <= n; i++));//每次排序之前和本来的顺序没什么关系,只和rank数组有关系,每一个数字如果大的话,就是rank数组大

    //获取新的rank数组



{

   tmp[sa[i]] = tmp[sa[i - 1]] + (compare(sa[i - 1],sa[i]) ? 1:0);//要用本来的rank来compare,所以先用tmp临时存储

}
for(int i = 0; i <= n; i++)

{

     rank[i] = tmp[i];

}//rank从0开始,代表每个的名次(重复的都已第一个名词计算)




bool compare_sa(int i,int j)

{

    if(rank[i] != rank[j] ) return rank[i] < rank[j];

   else

{

    int ri = i + k <= n? rank[i + k] : -1;//如果加了k之后超过了,那就是说后面没了,就是-1

    int rj = j + k <= n? rank[j+k] : -1;

 return  ri < rj;

}




高度数组,lcp[i]指的是后缀数组中第i个后缀和第i-1个后缀的最长公公前缀长度

利用到两个特点

性质1. 原来字符串中 S【i】和 S【rank【i】 - 1】是lcp【i】

而S【i + 1】和s【rank【i】 - 1 + 1】至少有lcp【i】 - 1长度的公共前缀和


性质2.如果S1<S2<S3那么s1和s3的公共前缀和就一定小于等于s2和s3的公共前缀和,举个例子

abca < a b d a < abda  

所以说s[i + 1]和 s【rank【i+1]-1】的公共前缀和一定大于等于上述的icp【i】 -1那个值

可以从i = 0开始计算公共前缀和,利用上述条件,可以推出复杂度是O(N)

代码也是来自《挑战程序设计竞赛》

int rank[maxn_n+1]//表示i开头的后缀的排名

void construct_lcp(string s,int *sa,int *lcp)//传入的是后缀数组和原来的字符串

{

int n = s.lenght();

for(int i = 0 ;i <= n; i++) rank[sa[i]] = i;//每一个后缀都不相等

int h = 0;

lcp[0] = 0;//初始条件

for(int i = 0; i < n; i++)

{

int j = sa[rank[i] - 1];

if(h >0) h--;

for(;j + h < n && i + h < n;h++)//第一次的时候h = 0,从头比较,之后可以利用前面的h

{

        if(s[j + h] != s[i + h] break;

}

lcp[rank[i] - 1] = h;


}

}


反正理解之后就套模板吧……就是可以在o(n)时间内求得第i和第i+1个后缀的lcp



求出高度数组之后,结合rmq,可以快速得到任意两个后缀的最长公共前缀,也是利用了性质2

如果rank[i] < rank[j] 那么 lcp(i,j) = min(lcp[x] i < = x < j)

你可能感兴趣的:(acm)