後綴數組的構造sa,rank和height數組

       

       智商餘額又不足了, 看着後綴樹的代碼看了一整天還是一知半解,看來暫時只能套模板了。

 後綴數組sa[] 保存了一個字符串的所有後綴, 並且按字典序排序。 sa[i] =k 的意思就是 排在第2i個的後綴, 在原串裏是以k開頭的後綴。

 rank[]數組是sa數組的逆運算, rank[k]=i的意思是從位置k開始的後綴在後綴數組中排第i位。

height數組保存了 後綴數組中相鄰兩個後綴的最大公共前綴, height[i] 的值是 sa[i-1]和sa[i] 的公共前綴長度。


      我們要把一個串建立成後綴數組,首先要在這個傳的末尾加一個特殊的字符,保證這個字符沒有出現過且它的值比前面的字符都要小。 (這樣方便兩個後綴大瀟的比較)。


     建立後綴數組採用倍增的方式, 我們需要知道以下先驗知識:

    

性質1.1 對k≥n,Suffix(i)<kSuffix(j) 等價於 Suffix(i)<Suffix(j)。
性質1.2 Suffix(i)=2kSuffix(j)等價於Suffix(i)=kSuffix(j) 且 Suffix(i+k)=kSuffix(j+k)。
性質1.3 Suffix(i)<2kSuffix(j) 等價於Suffix(i)<kSuffix(j) 或 (Suffix(i)=kSuffix(j) 且 Suffix(i+k)<kSuffix(j+k))。

其中kSuffix(j) ,表示以位置j 的後綴的長度爲k的前綴。

整個倍增算法的思路爲, 我要給所有的後綴排序,首先我給這個後綴中長度爲1的前綴排序,然後我再按前綴長度1*2=2 排序,然後我再按前綴長度2*2=4排序。
根據以上性質,在使用基數排序法時, 每一次給長度k的前綴排序時都利用到了按長度k/2的前綴排序的結果。

這裏再引入一下基數排序的概念:
舉個例子:
我現在有n個二位數, 我可以把他們看做各有兩個關鍵字 ,十位和個位, 先按個位排序,再按十位排序,整個數組就有序了。
比如如下數組:
11 ,23, 9,65, 72,35,55,84,99
先按個位排序,把它們放到0-9的“桶”裏
1      11
2    
3       23
4       84
5       65,35,55
6
7
8
9    9, 99
然後按桶的順序把它們組成新隊列
11 ,23, 84, 65,35,55,9,99 
這樣他們就按個位排好序了
然後再按十位排序
0       9
1      11
2      23
3      35
4       
5     55
6    65
7
8     84
9     99
得到新隊列:
9,11,23,35,55,65,84,99 
這樣就排好序了。
給所有後綴字符串排序也是這個道理, 初始時長度爲1的前綴是關鍵字, 然後前綴長度倍增, 每次都在前面排好序的基礎上排序。 直到所有後綴的大小關係都不一樣了,排序就完成了。

排好序後求height數組就很簡單了,height[i] = sa[rank[i]] 和sa[rank[i]-1]的最長相同前綴。
看着代碼再理解一下:
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
#define maxn 20010
#define ws wss
int wa[maxn],wb[maxn],wv[maxn],ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(int *r,int *sa,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]]++;
    for(i=1;i<m;i++) ws[i]+=ws[i-1];
    for(i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
//上面四行就是把長度爲1的前綴按基數排序的過程 , 可以在紙上走一下這個過程幫助理解

    for(j=1,p=1;p<n;j*=2,m=p)  //j是前綴長度, p是不同大小的後綴數量, m爲大於所有值的值
    {
        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;
   // 上面代碼使y保存了以第二關鍵字排序的結果 ,以第二關鍵字排序結果可利用上一個sa數組 , 在草紙上將上述過程模擬一遍即可理解
        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和y,rank值保存在x數組中,p爲不同串的個數
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
    return;
}
int rank[maxn],height[maxn];
void calheight(int *r,int *sa,int n) //求height過程
{
    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++);
    return;
}

int main()
{
    int sa[20010];//,note[20010];
    int n,i,j,k;
        int t1,t2;
		n=5;
       int note[6]={2,2,3,2,2,0};	   
        da(note,sa,n+1,4);
        calheight(note,sa,n);
		for(i=1;i<=n;i++)
			cout<<sa[i]<<endl;
		cout<<".."<<endl;
		for(j=1;j<=n;j++)
			cout<<height[j]<<endl;
     
 
 
 //   }
    return 0;
}

     整個過程大致就是上面這樣,有的細節還需要再理解。 利用後綴數組解決具體問題還得再通過實踐鍛鍊。
不過現在想解決“最大重複子序列” 是沒問題了, 直接求height的最大值即可
  

  

 

發佈了134 篇原創文章 · 獲贊 3 · 訪問量 27萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章