zoukankan      html  css  js  c++  java
  • 后缀树组 一个十分巧妙的东西,实际上是一种思想

    参考博客:

    https://www.cnblogs.com/jinkun113/p/4743694.html

    https://www.cnblogs.com/victorique/p/8480093.html

    https://www.cnblogs.com/jackdong/archive/2012/10/15/2724034.html

    https://www.cnblogs.com/shanchuan04/p/5324009.html

    https://www.cnblogs.com/zwfymqz/p/8413523.html

    后缀树组的精髓在于倍增算法、基数排序、和一些神奇的优化,巧妙的利用各个后缀之间的关系。理解了很长时间。

    基数排序在后缀数组中可以在O(n)的时间内对一个二元组(p,q)进行排序,其中p是第一关键字,q是第二关键字,比其他的排序算法都要优越。

    附上洛谷3809模板代码:

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 typedef unsigned int ui;
     4 typedef long long ll;
     5 typedef unsigned long long ull;
     6 #define pf printf
     7 #define mem(a,b) memset(a,b,sizeof(a))
     8 #define prime1 1e9+7
     9 #define prime2 1e9+9
    10 #define pi 3.14159265
    11 #define lson l,mid,rt<<1
    12 #define rson mid+1,r,rt<<1|1
    13 #define scand(x) scanf("%llf",&x) 
    14 #define f(i,a,b) for(int i=a;i<=b;i++)
    15 #define scan(a) scanf("%d",&a)
    16 #define dbg(args) cout<<#args<<":"<<args<<endl;
    17 #define inf 0x3f3f3f3f
    18 const int maxn=1e6+5;
    19 int n,m,t;
    20 using namespace std;
    21 char s[maxn];
    22 int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn],wt[30];
    23 void getsa()
    24 {
    25     for (int i=1;i<=n;++i) ++c[x[i]=s[i]];
    26     //c数组是桶 
    27     //x[i]是第i个元素的第一关键字 
    28     for (int i=2;i<=m;++i) c[i]+=c[i-1]; 
    29     //做c的前缀和,我们就可以得出每个关键字最多是在第几名 
    30     for (int i=n;i>=1;--i) sa[c[x[i]]--]=i; 
    31     for (int k=1;k<=n;k<<=1)
    32     {
    33         int num=0;
    34         for (int i=n-k+1;i<=n;++i) y[++num]=i;
    35         //y[i]表示第二关键字排名为i的数,第一关键字的位置 
    36         //第n-k+1到第n位是没有第二关键字的 所以排名在最前面 
    37         for (int i=1;i<=n;++i) if (sa[i]>k) y[++num]=sa[i]-k;
    38         //排名为i的数 在数组中是否在第k位以后
    39         //如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
    40         //所以i枚举的是第二关键字的排名,第二关键字靠前的先入队 
    41         for (int i=1;i<=m;++i) c[i]=0;
    42         //初始化c桶 
    43         for (int i=1;i<=n;++i) ++c[x[i]];
    44         //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了 
    45         for (int i=2;i<=m;++i) c[i]+=c[i-1];//第一关键字排名为1~i的数有多少个 
    46         for (int i=n;i>=1;--i) sa[c[x[y[i]]]--]=y[i],y[i]=0;
    47         //因为y的顺序是按照第二关键字的顺序来排的 
    48         //第二关键字靠后的,在同一个第一关键字桶中排名越靠后 
    49         //基数排序 
    50         swap(x,y);
    51         //这里不用想太多,因为要生成新的x时要用到旧的,就把旧的复制下来,没别的意思 
    52         x[sa[1]]=1;num=1;
    53         for (int i=2;i<=n;++i)
    54             x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
    55         //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字 
    56         if (num==n) break;
    57         m=num;
    58         //这里就不用那个122了,因为都有新的编号了 
    59     }
    60 }
    61 void geth()
    62 {
    63     int k=0;
    64     for (int i=1;i<=n;++i) rk[sa[i]]=i;
    65     for (int i=1;i<=n;++i)  
    66     {
    67         if (rk[i]==1) continue;//第一名height为0 
    68         if (k) --k;//h[i]>=h[i-1]+1;
    69         int j=sa[rk[i]-1];
    70         while (j+k<=n && i+k<=n && s[i+k]==s[j+k]) ++k;
    71         height[rk[i]]=k;//h[i]=height[rk[i]];
    72     }
    73 }
    74 int main()
    75 {
    76     gets(s+1);
    77     n=strlen(s+1);m=122;
    78     //n表示原字符串长度,m表示字符个数,ascll('z')=122 
    79     //我们第一次读入字符直接不用转化,按原来的ascll码来就可以了 
    80     //因为转化数字和大小写字母还得分类讨论,怪麻烦的 
    81     getsa();
    82     geth();
    83     for(int i=1;i<=n;i++)cout<<sa[i]<<" ";
    84 }

    另一份大佬详解:

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 
     5 using namespace std;
     6 
     7 const int N = 1000100;
     8 
     9 char s[N];
    10 int sa[N],t1[N],t2[N],c[N],rnk[N],height[N];
    11 //sa[i]  排名为i的是谁 -下标 
    12 //rnk[i] i的排名 -名次 
    13 //height[i]  排名为i的后缀与排名为i-1的后缀的最长公共前缀长度
    14 int n,m = 130;
    15 
    16 void get_sa() {
    17     int *x = t1,*y = t2;
    18     //基数排序 
    19     for (int i=0; i<m; ++i) c[i] = 0;
    20     for (int i=0; i<n; ++i) c[x[i] = s[i]]++;
    21     for (int i=1; i<m; ++i) c[i] += c[i-1];
    22     for (int i=n-1; i>=0; --i) sa[--c[x[i]]] = i;
    23     /*
    24     每次循环
    25     都将两个长度k子串合并为一个长度为2k的串
    26     其中前k个字符构成的子串的排名为第一关键字,后k个字符为第二关键字
    27     并求出合并后的字符串的排名 
    28     */
    29     //每次排名得到一共有多少个排名p,由p优化m的值。 
    30     for (int k=1; k<=n; k<<=1) {
    31         int p = 0;
    32         /*
    33         在上一轮中,第一关键字已经排好了, 此时只需要排第二关键字即可。 
    34         y数组是第二关键字排序的结果,存储的是2k长度的字符串的第一关键字的下标 
    35         n-k到n-1中所有的元素第二关键字为0 
    36         */
    37         for (int i=n-k; i<n; ++i) y[p++] = i;
    38         for (int i=0; i<n; ++i) if (sa[i] >= k) y[p++] = sa[i] - k;
    39         for (int i=0; i<m; ++i) c[i] = 0;
    40         for (int i=0; i<n; ++i) c[x[y[i]]]++;
    41         for (int i=1; i<m; ++i) c[i] += c[i-1];
    42         for (int i=n-1; i>=0; --i) sa[--c[x[y[i]]]] = y[i];
    43         swap(x,y);
    44         p = 1;
    45         x[sa[0]] = 0;
    46         for (int i=1; i<n; ++i)
    47             x[sa[i]] = (y[sa[i-1]]==y[sa[i]] && sa[i-1]+k<n && sa[i]+k<n &&    
    48             y[sa[i-1]+k]==y[sa[i]+k]) ? p-1 : p++;
    49         if (p >= n) break;//退出条件:一共有n种排名,说明有序 
    50         m = p;
    51     }
    52 }
    53 void get_height() {
    54     for (int i=0; i<n; ++i) rnk[sa[i]] = i; // sa排名为i的下标,rnk[i]下标为i的后缀的名次。 
    55     int k = 0; 
    56     height[0] = 0;  
    57     /*
    58     性质:height[rnk[i]] >= height[rnk[i-1]]-1
    59     理解为起始下标为i的后缀和i-1的后缀除了开头第一个字符不同,其余的相同的
    60     即suffix[i,n]与suffix[i-1,n]只有第一个字符不同。 
    61     所以suffix[i-1,n]匹配上的子串,与suffix[i,n]匹配的子串,中k-1个是相等的。
    62     */ 
    63     for (int i=0; i<n; ++i) {
    64         if (!rnk[i]) continue; 
    65         if (k) k--;
    66         int j = sa[rnk[i]-1]; // 前一个排名的后缀的开始位置 
    67         while (i+k<n && j+k<n && s[i+k]==s[j+k]) k++;
    68         height[rnk[i]] = k;
    69     }
    70 }
    71 int main() {
    72     scanf("%s",s);
    73     n = strlen(s);
    74     get_sa();
    75     for (int i=0; i<n; ++i) 
    76         printf("%d ",sa[i]+1);
    77     return 0;    
    78 }
  • 相关阅读:
    Java多线程之JUC包:ReentrantLock源码学习笔记
    Java多线程之JUC包:Semaphore源码学习笔记
    Java多线程之JUC包:CountDownLatch源码学习笔记
    Java多线程之JUC包:AbstractQueuedSynchronizer(AQS)源码学习笔记
    Java多线程之JUC包:CyclicBarrier源码学习笔记
    架构设计:企业总体架构要如何做?小白也能快速领悟的设计思想
    可参考才是有价值的,架构设计的技改之路从来都不容易
    架构设计:高并发读取,高并发写入,并发设计规划落地方案思考
    微服务手册:API接口9个生命节点,构建全生命周期管理
    百度api使用心得体会
  • 原文地址:https://www.cnblogs.com/randy-lo/p/12540049.html
Copyright © 2011-2022 走看看