后缀数组代码细节个人理解

后缀数组代码细节的理解(笔记)

在阅读蓝书后缀数组代码时,几个细节思考了好几天才理解。看懂后将自己的个人理解写出来,大佬勿喷.....

模板代码

// O(nlogn) 预处理
string r;
int SA[maxn], WS[maxn],  x[maxn], y[maxn];
void build_SA(int m)  //m为 ASCII 最大值代表循环边界
{
    int n = r.size();
    for(int i=0; i<m; ++i) WS[i] =0;
    for(int i=0; i<n; ++i) WS[ x[i] = r[i] ]++;
    //n = r.size() x[] 是対r 的ascii码呈现
    //for(int i=0;i<n;++i) printf("%d", x[i]); printf("\n");
    //for(int i='a';i<'z';i++) printf("i = %d, WS[i] = %d\n", i, WS[i]);
    for(int i=1; i<m; i++) WS[i] += WS[i-1];
    for(int i=n-1; i>=0; i--) SA[-- WS[x[i]] ] = i; //x[i] 和之前的字符共出现 WS[x[i]] 次
    for(int k=1; k<=n; k<<=1)
    {
        int p = 0; //Y存放长度为2j的字符串第二关键字排序结果,现在将第二关键字为0 的字符串预处理
        for(int  i = n - k; i < n ; ++i) y[p++] = i;
        for(int i=0; i<n; ++i) if(SA[i]>=k) y[p++] = SA[i] - k; 		//基数排序第二关键字
        for(int i=0; i<m; i++) WS[i]  =0;
        for(int i=0; i<n; i++) WS[x[y[i]]] ++;    //y[i]为第二关键字排序后排名的位置,x[y[i]]根据第二关键字排名位置找到第一关键字,WS[x[y[i]]] 为第一关键字统计。
        for(int i=0; i<m; i++) WS[i] += WS[i-1];
        for(int i=n-1; i>=0; --i) SA[-- WS[x[y[i]]]] = y[i]; 
        swap(x, y);//根据 SA 和 Y 计算新的x数组
        p  =1;
        x[SA[0]] = 0;
        for(int i=1; i<n; ++i) 			
            x[SA[i]] = y[SA[i-1]] == y[SA[i]] && y[SA[i-1]+k] == y[SA[i]+k] ? p-1: p++; //y为上阶段x数组。当k == 1 时 ,首尾两个字符分别比较确保相同。 //当k > 1时 y为上阶段 k 个字符的x 排序,比较y[i]相当于比较k个字符
        if(p>=n) break;
        m = p;
    }
}

基数排序

for(int i=0; i<n; ++i) WS[ x[i] = r[i] ]++;
for(int i=1; i<m; i++) WS[i] += WS[i-1];
   for(int i=n-1; i>=0; i--) SA[-- WS[x[i]] ] = i;

其中WS[]为第一关键字的统计量,第二个循环将统计第i个位置的字母的个数,和之前的比它小的字母个数总量。
当在第三个循环时 SA[-- WS[x[i]] ] = i,当循环到位置i时,该位置的字母以及之前比它小的字母个数即为它的排名。
-- WS[x[i]] 操作不仅是因为字符串从0开始,同时也是因为i位置字母以及被计算过因此统计量要减一。
下面表格为对应的排序名次。
WS统计

a b
5 7

排序后结果。第三行为对应-- WS[x[i]]操作后结果,同时每次--为下一个相同字母的排序。

a a b a a a a b
0 1 6 2 3 4 5 7
-1 1 5 1 2 3 4 6

循环中排序第二关键字

int p = 0; //Y存放长度为2j的字符串第二关键字排序结果,现在将第二关键字为0 的字符串预处理
for(int  i = n - k; i < n ; ++i) y[p++] = i;
for(int i=0; i<n; ++i) if(SA[i]>=k) y[p++] = SA[i] - k; 		//基数排序第二关键字

y[]表示第二关键字的第几名在第一关键字的位子。如y[4] = 0表示字符串首位a对应的第二关键字排序为4.
这是根据先前的排序SA[]得到对应的位置 向前寻找k位即为第一关键字对应的位置的第二关键字的排序。
第一个循环从n-k位开始,此时向后K位第二关键字为0(没有第二关键字),每个位置从0开始增加。为了便于表示假设k = 4 结果为

a a b a a a a b
4 5 6 7
0 1 2 3

第二个循环当SA[i] < k时向前寻找k位无法找到第一关键字,因此跳过。当i = 2找到SA[2] = 6,第一关键字排序为第二位的在6, 因此向前搜索4位SA[i] - k = 2第二关键字p++(4)名对应的第一关键字位置为2.

a a b a a a a b
2 4 5 6 7
4 0 1 2 3

循环结束对应结果为

a a b a a a a b
0 1 2 3 4 5 6 7
5 6 4 7 0 1 2 3
此时根据上一次排序的结果排序第二关键字已经完成了

根据y[]対第一关键字进行基数排序

for(int i=0; i<n; i++) WS[x[y[i]]] ++;    //y[i]为第二关键字排序后排名的位置,x[y[i]]根据第二关键字排名位置找到第一关键字,WS[x[y[i]]] 为第一关键字统计。
for(int i=0; i<m; i++) WS[i] += WS[i-1];
for(int i=n-1; i>=0; --i) SA[-- WS[x[y[i]]]] = y[i]; 		//根据 SA 和 Y 计算新的x数组

前面由y[]相当与已经将第二关键字排序完毕得到结果为:
R[]代表对应位置的排序。 R[SA[i]] = i;

0 1 2 3 4 5 6 7
r a a b a a a a b
SA 0 1 6 2 3 4 5 7
R 0 1 3 4 5 6 2 7
Y 5 6 4 7 0 1 2 3
排序 50 60 20 70 32 05 16 37
y[i]得知对应的第一关键字的位置,x[y[i]]得到对应位置的第一关键字的内容,WS[x[y[i]]] ++则是対第一关键字数量的统计。
SA[-- WS[x[y[i]]]] = y[i] 举个例子当 i = 7 ,就是将第二关键字最后一位37取出排序,y[7] = 3, x[3] = 'a', SA[--WS['a']] = y[7] = 3.排名为5的位置为3.

从新计算X数组

swap(x, y);
x[SA[0]] = 0;
        for(int i=1; i<n; ++i) 			
            x[SA[i]] = y[SA[i-1]] == y[SA[i]] && y[SA[i-1]+k] == y[SA[i]+k] ? p-1: p++;

此时计算得出的x数组表示的是长度为2*k的字符串在相应位置对应的排名,同时将相同的字符串排名也相同。
当k = 1时 比较 y[SA[i]] y[SA[i-1]], y[SA[i]+1] y[SA[i-1]+1]判断字符串是否相同。
当k > 1时 y[]为上一轮的长度为k字符串的对应X数组,每一个y[i]对应的字符串长度为k,因此也只需比较两个位置即可。

posted on 2021-03-23 18:44  安乐最值钱  阅读(88)  评论(0)    收藏  举报

导航