美文网首页算法
字符串问题一大利器——后缀数组详解

字符串问题一大利器——后缀数组详解

作者: 信息学小屋 | 来源:发表于2020-05-02 09:56 被阅读0次

今天,我们来介绍一下解决字符串问题的一大利器——后缀数组。

几个定义

为了下文表示的方便,我们需要先达成几个共识:
1、字符串的位置从0开始标号,一直到n-1
2、后缀i,表示从i...n-1这些字符按顺序组成的字符串。显然,该字符串是原字符串的一个后缀。
3、字符串的大小比较:从第0个位置开始比较。如果相同,继续往后比;如果不同,则当前位置字符ASCII码大的对应字符串更大。如果仍无法比较大小,则长度长的字符串更大,否则两者相等。举个栗子:

Example

后缀数组是啥

后缀数组,顾名思义,就是把一个字符串的每一个后缀都进行排序。在这个算法中,我们需要处理得到两个数组,第一个数组记录的排名第i的后缀是哪一个(sa数组),第二个数组是第i个后缀的排名是多少。
举个栗子:

Example

sa数组就是我们通常所说的后缀数组,而rank数组可以通过sa数组快速求得。我们这个算法就是为了快速地求出sa数组的值。

后缀数组怎么求

求解后缀数组有两种方法:倍增算法,DC3算法。
其中,倍增算法的时间复杂度是O(NlogN)的,程序简单,算法过程易于理解。而DC3算法的时间复杂度是O(N),数据量大的时候,效率比倍增算法有显著提升,但是缺点在于DC3算法原理较难理解,代码冗长。所以,这里我们讲解倍增算法。下面进入正题(为了防止格式错乱,采用图片的形式来讲解)。

算法讲解

Code

#include <bits/stdc++.h>
#define maxn 200005
using namespace std;
int a1[maxn],a2[maxn],ss[maxn];
bool cmp(int *r,int i,int j,int len)
{
    return (r[i]==r[j] && r[i+len]==r[j+len]);
  //判断两个字符串时候完全一样 
  //即前缀和后缀是否一样 
}
void sa_get(int *r,int *sa,int n,int m)
{
    int i,j,p,*x=a1,*y=a2,*t;//使用指针在后面交换的时候直接交换指针
    //x[i]相当于rank数组,表示第i个排第几 
    //y[i]表示当前按照后半部分排好的序列,储存的是前半部分的起点
    for (i=0;i<m;++i)ss[i]=0;
    for (i=0;i<n;++i)ss[x[i]=r[i]]++;
    for (i=1;i<m;++i)ss[i]+=ss[i-1];
    for (i=n-1;i>=0;--i)sa[--ss[x[i]]]=i;
  //对后缀进行排序(刚开始时只有一个字符) 
  //桶排不会改变元素的相对位置 
    for (j=1,p=1;p<n;j*=2,m=p)
    {
        for (p=0,i=n-j;i<n;++i)y[p++]=i;//后面j个的后缀为0,一定最小 
        for (i=0;i<=n;++i)if (sa[i]>=j)y[p++]=sa[i]-j;
    //按照后缀从小到大的顺序排列前缀 
    //先确定后缀的顺序
        for (i=0;i<m;++i)ss[i]=0;
        for (i=0;i<n;++i)ss[x[y[i]]]++;
        for (i=1;i<m;++i)ss[i]+=ss[i-1];
        for (i=n-1;i>=0;--i)sa[--ss[x[y[i]]]]=y[i];//对前缀进行排序 
        for (t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;++i)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;//更新x的值 
    }
    return;
}
char s[maxn];
int sa[maxn],sum,num[maxn];
int main ()
{
    scanf ("%s",s);
    sum=strlen(s);
    for (int b=0;b<sum;++b)num[b]=s[b]-'a'+1;
    //由于在末尾添加最小字符,所以这里字符串从0开始
    //排序后的sa数组第一个是我们添加的字符,所以有效数组从下标1开始    
    num[sum++]=0;//记得一定要在最后补一个最小的字符
    sa_get(num,sa,sum,128);
    for (int b=1;b<sum;++b)printf ("%d ",sa[b]+1);puts("");
    return 0;
}

写在最后

后缀数组的精华在于用sa数组求出height数组,而height数组有着各式各样的性质和用法,我将在下期为大家带来。

相关文章

  • 字符串问题一大利器——后缀数组详解

    今天,我们来介绍一下解决字符串问题的一大利器——后缀数组。 几个定义 为了下文表示的方便,我们需要先达成几个共识:...

  • 后缀数组之多字符串问题

    上期,我们主要讲解了后缀数组在单字符串问题上的应用。在多字符串问题上,后缀数组是否仍然有优秀的表现呢?答案显然是肯...

  • 后缀树(suffix tree & array)

    定义:后缀数组(suffix array)是将字符串的所有后缀进行排序放入数组中。后缀树(suffix tree)...

  • 字符串

    字符串DP 字符串匹配 后缀数组 后缀数组的注意点:1 两个串拼接在一起。2 height数组的性质。POJ 15...

  • c语言解决最长重复子串问题

    1.解题思路 最大后缀方法思路: 用字符串指针数组保存用户输入的字符串的所有后缀字符串; 将后缀字符串集合进行排序...

  • 字符串算法

    求一个字符串的前缀与另一个字符串的后缀的最大相同子串 一个关于字符串前后缀的神奇数组:next 数组 Leetco...

  • 单字符串问题?后缀数组来啦!

    前两期,我们重点介绍了后缀数组中sa、rank、height数组的求法。这些数组都具有优秀的性质,我来向大家介绍几...

  • JavaScript之数据类型

    二、数据类型 目录:字符串类型详解、数组类型详解、对象类型详解、分支和循环详解、Map和Set集合(ES6新特性)...

  • 2018-04-17js对象详解

    1. 对象详解 2. 字符串详解3. 数组详解 1.对象的创建: (1)使用构造函数创建内置对象:eg:var m...

  • 最长公共子串

    问题: 找出最长、连续的子字符串 思路: 遍历X、Y的所有子字符串,找出最长公共后缀,则最长公共后缀的长度就是最长...

网友评论

    本文标题:字符串问题一大利器——后缀数组详解

    本文链接:https://www.haomeiwen.com/subject/otohghtx.html