后缀数组(理解)

一、基本概念

    后缀:用  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,

我们要比较str1和str2的大小,显然我们只需要比较x1和x2的大小(这里把x称为第一关键 字),若x1==x2,

则比较y1和y2的大小(这里把y称为第二关键 字),据此就可以判断str1和str2的大小。

  注意:

1、在比较过程中,后续的字符串长度不够,就用一个长度为2^k-2,排名为0 的空字符串代替补足,

2、当2^k-1>=字符串长度时,得到的排名就是所有后缀的排名

具体比较过程如下图:

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

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

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

三、最长公共前缀--LCP

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

    

四、代码

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

    

#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();
    Getheight();
    for (int i = 1; i <= len; ++i) //输出排名为i的起始位置的下标
        printf("%d\n", 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

原文地址:https://www.cnblogs.com/-citywall123/p/11385685.html

时间: 2024-07-31 11:36:21

后缀数组(理解)的相关文章

后缀数组模板(理解)

字符串的处理真可谓是博大精深,后缀数组这种数据结构我花了两天时间才明白了其构造的过程.主要是代码不好理解. 数据结构: 1.sa数组,就是后缀数组,按照字典序排列,其意义为:sa[i]=k,排第i名的子串是从k位开始的. 2.rank名次数组,其意义为:rank[i]=k,以i为起点的子串排名为k. 很容易看出来两者可以相互转化. 求这两个数组的过程是基于基数排序,计数排序的方法. 下面是一个大牛的注释版.其中我在补充点: 1.对于求y[]这个数组,因为已经知道上次长为j的子串比较结果,那么这次

后缀数组倍增法理解

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]; } void da (int *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

poj 2774 Long Long Message 后缀数组LCP理解

题目链接 题意:给两个长度不超过1e5的字符串,问两个字符串的连续公共子串最大长度为多少? 思路:两个字符串连接之后直接后缀数组+LCP,在height中找出max同时满足一左一右即可: #include<iostream> #include<cstdio> #include<cstring> #include<string.h> #include<algorithm> #include<map> #include<queue&

后缀数组:倍增法和DC3的简单理解

一些定义:设字符串S的长度为n,S[0~n-1]. 子串:设0<=i<=j<=n-1,那么由S的第i到第j个字符组成的串为它的子串S[i,j]. 后缀:设0<=i<=n-1,那么子串S[i,n-1]称作它的后缀,用Suffix[i]表示. 串比较:对于两个串S1,S2,设长度分别为n1,n2.若存在一个位置i,使得对于0<=j<i满足S1[j]=S2[j]且S1[i]<S2[i],那么我们称S1<S2.如果S1是S2的一个前缀,那么也有S1<S2

hdu 3518 Boring counting 后缀数组LCP

题目链接 题意:给定长度为n(n <= 1000)的只含小写字母的字符串,问字符串子串不重叠出现最少两次的不同子串个数; input: aaaa ababcabb aaaaaa # output 2 3 3 思路:套用后缀数组求解出sa数组和height数组,之后枚举后缀的公共前缀长度i,由于不能重叠,所以计数的是相邻height不满足LCP >= i的. 写写对后缀数组倍增算法的理解: 1.如果要sa数组对应的值也是1~n就需要在最后加上一个最小的且不出现的字符'#',里面y[]是利用sa数

【tyvj1860】后缀数组

描述 我们定义一个字符串的后缀suffix(i)表示从s[i]到s[length(s)]这段子串.后缀数组(Suffix array)SA[i]中存放着一个排列,满足suffix(sa[i])<suffix(sa[i+1]) 按照字典序方式比较定义height[i]表示suffix(sa[i])与suffix(sa[i-1])之间的最长公共前缀长度,其中height[1]=0你的任务就是求出SA和height这两个数组.字符串长度<=200000 输入格式 一行,为描述中的字符串(仅会出现小写

后缀数组 DC3构造法 —— 详解

学习了后缀数组,顺便把DC3算法也看了一下,传说中可以O(n)复杂度求出文本串的height,先比较一下倍增算法和DC3算法好辣. DC3 倍增法 时间复杂度 O(n)(但是常数很大)   O(nlogn)(常数较小) 空间复杂度   O(n)    O(n) 编程复杂度    较高   较低 由于在时间复杂度上DC3的常数比较大,再加上编程复杂度比较高,所以在解决问题的时候并不是最优选择.但是学到了后缀数组还是补充一下的好点. DC3算法的实现: 1:先把文本串的后缀串分成两部分,第一部分是后

后缀数组构造

读了罗穗的论文,终于知道后缀数组怎么构造了,还反复打了五遍,打得很痛苦才最终理解. 终于体会到XY的痛苦了.实在是一篇OI生涯中最难懂的代码orz看来两天.一下是经过稍微加长的稍微易理解的代码(其实差不多好吧). 具体注释看 网址 1 #include<cstdio> 2 #include<string.h> 3 #include<iostream> 4 using namespace std; 5 6 struct suffix_array 7 { 8 const l

后缀数组之hihocoder 重复旋律1-4

蒟蒻知道今天才会打后缀数组,而且还是nlogn^2的...但基本上还是跑得过的: 重复旋律1: 二分答案,把height划分集合,height<mid就重新划分,这样保证了每个集合中的LCP>=mid,套路板子题 // MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> using