zoukankan      html  css  js  c++  java
  • 后缀数组记录

    后缀数组

    参考:

    彻底弄懂后缀数组

    后缀数组——处理字符串的有力工具

    知乎

    以洛谷P3809 【模板】后缀排序为例,看了两天才理解了七成的数据结构┭┮﹏┭┮。

    先上经典图

    后缀数组是一个搞出字符串所有后缀的一个字典序排名。字典序就不解释了,不懂字典序你也不会来搜后缀数组了。。。然后

    SA【】数组,是一个排行榜,SA[i] 表示排名i的后缀的开始位置。

    rank【】是排名,rank【i】记录的是位置i的后缀字符串的排名

    x【】y【】分别是关键字,buc【】是基数排序的桶,基数排序不是桶排,但是有共同点

    SA和rank是一个逆运算。这点很重要rank[SA[i]] == i, SA[rank[i]] == i;

    先第一步,先不管代码,弄懂上面那张图。aabaaaab按照大小关系离散化成11211112。

    离散化:在比较10,500,90000的大小的时候,有的时候我们操作这么大的数不方便,比如数组标记的时候

    把数据等价为1,2。就像上面的两位数合并后,计算rank再合并1

    两两合并,这样就有了所有后缀前两个字母的排名,再次合并就有了前四个字母的排名,后面不够补0,这样补影响答案。最后不断倍增就获得了整个后缀的排名。

    比如

    	a  a  b  a  a  a  b
    	1  1  2  1  1  1  2 	这是单个字符的排名
    	11 12 21 11 11 12 20	每个位置和后面跳【一】个位置合并,再离散化排名
        1   2  4  1  1  2  3     现在每个位置和后面跳【二】个位置合并
        14 21 41 11 12 13 20 30	 这样在求排名就是每个位置长度为4的后缀的排名,不断倍增就能得出答案
        					   这里应该对照上图以前看。
        					   不断合并知道rank都不同。因为所以位置的后缀长度必定是不同的,所以字符串不可能相等
    

    然后是代码,注释写代码里面了

    #include <bits/stdc++.h>
    
    using namespace std;
    const int maxn = 1e6 + 7;
    
    int n, m, len, SA[maxn], rnk[maxn], buc[maxn], x[maxn], y[maxn];
    
    char str[maxn];
    
    /**
    后缀数组的构建
    */
    
    void build() {
        m = 150;    ///边界,我们字符串是ASCII初始127,稍微大一点没影响。
    
        ///基数排序,首先清空桶
        for(int i = 0; i < m; i ++ ) { buc[i] = 0; }
        ///开始对数字每一个字符,即还未倍增的状态进行标记,存到x数组备用
        for(int i = 0; i < n; i ++ ) { buc[ x[i] = str[i] ] ++; }
        ///前缀和,这样后面能方便获得排名
        for(int i = 1; i < m; i ++ ) { buc[i] += buc[i - 1]; }
        ///计算SA值,SA[i]表示排名i的字符下标,这里还未倍增所以是单个字符的排名。
        ///遍历每个x[i],就是遍历数组的每个字符,在桶里的位置,前缀和后 buc[] 就是排名
        for(int i = 0; i < n; i ++ ) { SA[ --buc[ x[i] ] ] = i; }
        ///然后开始倍增了
        for(int k = 1; k <= n; k <<= 1) {
            int p = 0;
    
            ///难点1:这部分的对第二关键字(个位)基数排序
            ///y数组存的是第二关键字对应第一关键字的下标, y[0] = i 表示排名0的第二关键字的第一关键字位置,他们跨越了一个k的距离
            ///n-k 到 n-1 这部分没有第二关键字,就是经典图中的0,所以排名先放进y
            for(int i = n - k; i < n; i ++ ) { y[p++] = i; }
            ///SA[i]表示排名i的字符串在哪,数值表示的是下标,SA[i]>=k表示字符串开始位置在k以后
            ///这些【位置】是第二关键字,SA[i] - k就是找这些跨越长度k的对应的第一关键字的位置。不满足整个条件也没有对应的第一关键字
            ///想一想,我们不断倍增,基数排序,答案都是记在每一个后缀的起点位置的,这正是我们的目的所在
            ///下面这个循环0~n-1不能变,因为SA已经排好,按第一关键字的排序取第二关键字,
            ///因此y[i]才是排名,排名小的越前面。
            for(int i = 0; i < n; i ++ ) { if(SA[i] >= k) y[p++] = SA[i] - k; }
    
            ///接下来这一块是对第一关键字(十位)排名的计算
            ///清空桶
            for(int i = 0; i < m; i ++ ) { buc[i] = 0; }
            ///x[ y[i] ] 是第一关键字的...离散化后的映射值把,这个词了比较形象,第一次是字符,后面是那个排名
            ///表示大小的数值,比如aba排完121,不断倍增在不断离散出新的排名。
            for(int i = 0; i < n; i ++ ) { buc[ x[ y[i] ] ]++; }    ///
            for(int i = 1; i < m; i ++ ) { buc[i] += buc[i - 1]; }
            for(int i = n - 1; i >= 0; i -- ) { SA[ --buc[ x[ y[i] ] ] ] = y[i]; }
            ///在交换前y已经没用,交互后原来的y是x变成有用的了,x是没用的y,拿来存新的排名,所以下面动的是x[]
            swap(x, y);
            p = 1; x[ SA[0] ] = 0; ///最开始的排名,是为了防止下面-1的越界
            ///下面把字符串相同的变为排名相等,
            for(int i = 1; i < n; i ++ ) {
                if(y[ SA[i - 1] ] == y[ SA[i] ] && y[ SA[i - 1] + k] == y[ SA[i] + k]) {
                    x[ SA[i] ] = p - 1;
                } else {
                    x[ SA[i] ] = p ++;
                }
            }
            ///所有rnk都不同了,就不用倍增了
            if(p >= n) { break; }
            m = p;
        }
        for(int i = 0; i < n; i ++) { rnk[ SA[i] ] = i; }
    }
    
    int main()
    {
        scanf("%s", str);
        n = strlen(str);
        build();
        for(int i = 0; i < n; i ++ ) {
            printf("%d%c", SA[i] + 1, " 
    "[i == n - 1]);
        }
        return 0;
    }
    
    
  • 相关阅读:
    ViewPager 滑动页(一)
    Fragment中Button的android:onClick 无法监听相应
    Button的四种Click响应方法
    环形图 自定义(一)
    Progress 自定义(一)-shape
    Button 自定义(一)-shape
    客户机页表遍历
    KVM的ept机制
    linux内核源码中两个重要的宏
    总结
  • 原文地址:https://www.cnblogs.com/Q1143316492/p/8733362.html
Copyright © 2011-2022 走看看