1. 程式人生 > >字尾陣列詳解+模板

字尾陣列詳解+模板

字尾陣列

SA[] 第幾名是誰

字尾陣列:字尾陣列 SA 是一個一維陣列, 它儲存 1..n 的某個排列 SA[1] ,SA[2],……,SA[n],並且保證 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n 。也就是將 S 的 n 個字尾從小到大進行排序之後把排好序的字尾的開頭位置順次放入 SA 中。

Rank[] 誰是第幾名名次陣列:名次陣列 Rank[i]儲存的是 Suffix(i)在所有後綴中從小到大排列的“名次 ” 。

r[]:原始資料j當前字串的長度,每次迴圈根據2個j長度的字串的排名求得2j長度字串的排名.

y[]:指示長度為2j的字串的第二關鍵字的排序結果,通過儲存2j長字串的第一關鍵字的下標進行指示.

wv[]:2j長字串的第一關鍵字的排名序號.

ws[]:計數陣列,計數排序用到.

x[]:一開始是原始資料r的拷貝(其實也表示長度為1的字串的排名),之後表示2j長度字串的排名.

p:不同排名的個數.

片段

1.對長度為1的字串進行排序(函式的第一步)

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;

①用的是基數排序,也可以使用其它的排序

②r[]儲存原本輸入的字串,x[]是對r[]的ASCII呈現(便於排序)

③m是一個估計數字,代表ASCII最大值,在迴圈中做邊界

④n在這裡是字串的長度+1,後面的加加減減有所體現(貌似不介意直接用字串的長度)

⑤最後一行比較難懂,但實踐證明它確實是正確的,sa[i]=j表示第i名是j。

ws[i]是對第i及之前字元出現次數的累加,越往後ws[i]越大,而且對應的字元數值越大,舉個例子,如果某一字串為aaabaa,則a出現的次數為5,b出現的次數為1,按上述原理,可以看做ws[a]=5,ws[b]=6,固然a都在前5名,b在第六名。

對aabaaaab進行輸出後為801345627,按照sa的定義對應起來

aabaaaab ~

23845679 1  非常正確

理解了這個,最後一行就能明白了

2.進行若干次基數排序

因為前面排序的名次可能有重複,所以要再進行若干次,直到所有的名次都不再相同

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++;
}

相對於上面函式的第一步來說,這一坨程式碼更加複雜了

①從最外層迴圈可以看出,j是處於倍增狀態的,代表正在比較的每一小段字串的長度

②迴圈內的第一行,迴圈了j-1次,是對後面幾個數的提前處理(其第二關鍵字都為0)如圖

即所有加0的數

③第二行,再翻上去看一眼sa的作用。首先要明白這一行拋棄了一些東西,

 

由於是對第二關鍵字的排序,第一關鍵字先不看,所以有一條件if(sa[i]>=j)

這條語句後面y[p++]=sa[i]-j,要減去j也是因為這個

到這裡,第二關鍵字的排序就完成了

④開始第一關鍵字的排序

假設需要排序的數為92 71 10 80 63 90

那麼y[]=3 4 6 2 1 5 即對第二關鍵字排序後名次遞增所對應的序號

      x[]=10 80 90 71 92 63 即對第二關鍵字排序的結果

for(i=0; i<n; i++) wv[i]=x[y[i]];將x[]陣列拷貝到wv[]中

完整的程式碼(參考理解)

#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 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 */ 
void da(const char 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]]++;//以字元的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;
}
int sa[MAXN],Rank[MAXN],height[MAXN];
//求height陣列
/**< str,sa,len */
void calheight(const char *r,int *sa,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,sa,len+1,130);
            calheight(str,sa,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;
}