• 后缀数组(理解)


    一、基本概念

        后缀:用  suff[i]  表示,是指从某个位置 i 开始到整个串末尾结束的一个子串

      后缀数组:用 sa[i] 表示,是指所有后缀在排完序后,排名为i的后缀在原串中的位置。   sa[排名]=位置
      名次数组:用 rank[i] 表示,是指所有后缀在排序完后,原字符串中第i个后缀现在的排名。   rank[位置]=排名

      

      比如字符串aabaaaab$,(我们习惯在字符串后面加一个特殊字符$,表示字符串的结尾)他的所有后缀、位置、排名如下:

      后缀           位置      排名

      suff[ 1 ] :  aabaaaab$    sa[ 4 ] = 1    rank[ 1 ]=4

      suff[ 2 ] :  abaaaab$      sa[ 6 ] = 2    rank[ 2 ]=6

      suff[ 3 ] :  baaaab$     sa[ 8 ] = 3     rank[ 3 ]=8

      suff[ 4 ] :  aaaab$       sa[ 1 ] = 4     rank[ 4 ]=1

      suff[ 5 ] :  aaab$      sa[ 2 ] = 5     rank[ 5 ]=2

      suff[ 6 ] :  aab$           sa[ 3 ] = 6     rank[ 6 ]=3

      suff[ 7 ] :  ab$        sa[ 5 ] = 7    rank[ 7 ]=5

      suff[ 8 ] :  b$          sa[ 7 ] = 8    rank[ 8 ]=7

      字符串大小的比较

      字符串的比较是逐位按字典序比较,若字典序相同,则比较下一位,否则直接分出大小

      1、b<aaaaaaaaaaaaaaaaaaaa

      2、aab<aabc

    二、倍增法求后缀数组

    倍增思想:(这里倍增的是字符串长度
     
    次比较的是所有长度为1的字符串,得出所有长度为1的字符串排名
     
    次比较的是所有长度为2的字符串,我们用一个长度为2的窗口[ l , l+len-1 ]]来表示这个字符串。显然,这个字符串是由两个相邻且长度为1的字符串拼接组成的,长度为2的字符串排名由两个长度为1的字符串的排名x和y组成xy
     
    次比较的是所有长度为4的字符串,我们用一个长度为4的窗口[ l , l+len-1 ]]来表示这个字符串。显然,这个字符串是由两个相邻且长度为2的字符串拼接组成的,长度为4的字符串排名由两个长度为2的字符串的排名x和y组成xy
     
    次比较的是所有长度为8的字符串,我们用一个长度为8的窗口[ l , l+len-1 ]]来表示这个字符串。显然,这个字符串是由两个相邻且长度为4的字符串拼接组成的,长度为8的排名由两个长度为4的字符串的排名x和y组成xy
    ......
    第k次比较的是所有长度为2^k-1的字符串,长度为2^k-1的排名是由两个长度为2^k-2的字符串的排名x和y组成 xy,     xy的排位大小就是对应字符串的排名大小(这里对xy排序的时候采用基数排序)
     
     
    如何通过排名来比较字符串的大小?
    举个例子:两个长度为4的后缀str1和str2,   
    str1是由两个长度为2的字符串拼接组成,他们的排名分别为x1和y1,
    同样,str2也是由两个长度为2的字符串拼接组成,他们的排名分别为x2和y2,
     
    我们要比较str1str2的大小,显然我们只需要比较x1和x2的大小(这里把x称为第一关键 字),若x1==x2,
    则比较y1和y2的大小(这里把y称为第二关键 字),据此就可以判断str1str2的大小。
     

      注意:

    1、在比较过程中,后续的字符串长度不够,就用一个长度为2^k-2,排名为0 的空字符串代替补足,
     
    2、当2^k-1>=字符串长度时,得到的排名就是所有后缀的排名
     
    具体比较过程如下图:
     

         上面通过排名来比较字符串的大小,采用的是基数排序的方法,这里介绍一下基数排序

    时间复杂度:O(len),len是字符串的长度

    所谓基数排序,就是从最低位开始,先按个位排,再排十位,再排百位……,若出现相等,则把先出现的数视为较小,放在前面

     

     三、最长公共前缀--LCP

        height[i]:表示suff[sa[i]]suff[sa[i1]]的最大公共前缀,也就是排名完后两个相邻的后缀的最长公共前缀。  

        

    四、代码

        代码的实现过程还不太懂,先把板子记下来

        

    #include<iostream>
    #include<string.h>
    #include<string>
    #include<algorithm>
    #include<math.h>
    #include<string>
    #include<string.h>
    #include<vector>
    #include<utility>
    #include<map>
    #include<queue>
    #include<set>
    #define mx 0x3f3f3f3f
    #define ll long long
    using namespace std;
    const int N = 1e6 + 10;
    int num = 122, len;
    int fir[N], sec[N], t[N], sa[N],height[N];
    //first[]相当于是rank数组,first[i]表示从位置i开始到结尾的后缀排名是first[i];    first[位置]=排名
    //sa[i]表示排名为i的后缀的起始位置是从sa[i]开始到结尾的
    //height[i]表示排名为i的后缀和排名为i-1的后缀的最长公共前缀的长度是height[i]
    char s[N];
    inline void SA() 
    {
        for (int i = 1; i <= num; ++i) t[i] = 0; 
        for (int i = 1; i <= len; ++i) ++t[fir[i] = s[i]];
        for (int i = 1; i <= num; ++i) t[i] += t[i - 1];
        for (int i = len; i >= 1; --i) sa[t[fir[i]]--] = i; 
        for (int k = 1; k <= len; k <<= 1) {
            int cnt = 0;
            for (int i = len - k + 1; i <= len; ++i) sec[++cnt] = i;
            for (int i = 1; i <= len; ++i) if (sa[i] > k) sec[++cnt] = sa[i] - k;
            for (int i = 1; i <= num; ++i) t[i] = 0;
            for (int i = 1; i <= len; ++i) ++t[fir[i]];
            for (int i = 1; i <= num; ++i) t[i] += t[i - 1];
            for (int i = len; i >= 1; --i) sa[t[fir[sec[i]]]--] = sec[i], sec[i] = 0;
            swap(fir, sec);
            fir[sa[1]] = 1, cnt = 1;
            for (int i = 2; i <= len; ++i) 
                fir[sa[i]] = (sec[sa[i]] == sec[sa[i - 1]] && sec[sa[i] + k] == sec[sa[i - 1] + k]) ? cnt : ++cnt;
            if (cnt == len) break;
            num = cnt;
        }
    }
    void Getheight() {
        int j, k = 0;   //目前height数组计算到k
        for (int i = 1; i <= len; i++) {
            if(k) k--;  //由性质得height至少为k-1
            int j = sa[fir[i] - 1];   //排在i前一位的是谁
            while(s[i + k] == s[j + k]) k++;
            height[fir[i]] = k;
        }
    }
    int main() 
    {
        scanf("%s", s + 1);//字符串是从位置1开始读入
        len = strlen(s + 1);
        SA();//如果有多组输入,要多次调用SA()函数,只需要把对num=122重新赋值即可;不许要对fir,sa等数组置零操作
        Getheight();
        for (int i = 1; i <= len; ++i) //输出排名为i的起始位置的下标
            printf("%d
    ", sa[i]);
        // for(int i=1;i<=len;i++)//从小到大输出字符串s的所有后缀
        // {
        //     for(int j=sa[i];j<=len;j++)
        //         cout<<s[j];
        //     cout<<endl;
        // }
        cout<<"--------------"<<endl;
        for(int i=1;i<=len;i++)//位置从左往右输出所有后缀的排名
            cout<<fir[i]<<endl;
        cout<<"-------------"<<endl;
        for(int i=1;i<=len;i++)//输出相邻后缀的最长公共前缀长度
            cout<<height[i]<<endl;
    
    
        return 0;
    }

      以上代码转载自https://www.cnblogs.com/lykkk/p/10520070.html,里面详细的介绍了代码的实现过程,有时间在详细理解,orz

  • 相关阅读:
    Laravel笔记
    Mysql函数大全
    nginx中文文档
    解析富文本框
    VSCode的C++环境配置,多cpp在同一文件夹(json方式)
    UltraISO光盘刻录
    plog日志库(c++)
    .NET Core安装
    Halcon深度学习——奇异值检测
    C++命名规范
  • 原文地址:https://www.cnblogs.com/-citywall123/p/11385685.html
Copyright © 2020-2023  润新知