作者:Andy__lee

链接:https://blog.nowcoder.net/n/6b4a93e186ed4a358321de6a7c3b4f19

来源:牛客网

定义

维基百科 - 后缀数组

让我们来看一下 wiki 上的定义:

在计算机科学里, 后缀数组(英语:suffix array)是一个通过对字符串的所有后缀经过排序后得到的数组。此数据结构被运用于全文索引、数据压缩算法、以及生物信息学。

令字符串

表示
的子字符串,下标从
的后缀数组
被定义为一个数组,内容是
的所有后缀经过字典排序后的起始下标。

满足

求法

大体思想

这玩意可以使用倍增来求,当然也有常数比较大的

算法。

中心思想就是假设我们求出了考虑每个后缀前

个的字典序,现在我们需要扩展到
的情形。首先
的时候十分简单,就是对应的 ASCII 码。考虑每次
,相当于要把每个后缀当做一个二元组来排序。我们考虑对于每个后缀指定两个变量

目前考虑的长度下

指这个后缀的排名,
指这个后缀后
个字母的排名。显然
可以由上一轮的
得到(后面会单独说)。然后我们就可以用上一轮的
当做二元组来排序,就能更新出这次的
了。

相信大家看完后肯定是一脸 mb,接下来我来详细的说一下算法过程。

算法过程

首先我们来定义一些变量:

表示排名为
的后缀的位置,
表示第
个后缀的排名,
表示每次倍增里的第二关键字排名为
的位置,我们设这个字符串
的长度为

首先按照定义,显然有

也就是说明了这两个数组可以互相

推。

开始时

我们拿来排序的二元组是
来排序,显然 ASCII 码小的会在前面,相同的靠前的会在前面。然后我们开始倍增。考虑已经求出了长度为
的答案,考虑去更新
的答案。

现在我们考虑如何求出

(第二关键字)。代码如下:
p = 0;
FOR(i,1,w) tp[++p] = N-w+i;
FOR(i,1,N) if(sa[i] > w) tp[++p] = sa[i]-w;

首先对于长度

的后缀,一定字典序会在前面,我们先都拿出来。

然后对于长度

的后缀,我们发现后缀
的后
个字符就是上一轮后缀
的前
个字符。(考虑这一轮
个字符的意义下)

所以代码就是按照上一轮的顺序从小到大枚举了每个长度

的后缀,然后找到第二关键字的对应位置定位过去即可。

之后我们搞个比较高效的排序(基数排序)排一下。

然后因为在这个倍增过程中可能有的后缀的

暂时相同,但是最后的答案一定是互不相同,于是我们需要对于这一轮的结果去一下重(重新分配
编号)
std::swap(tp,rk); // rk 没用了,当然这里最好写指针交换
rk[sa[1]] = p = 1;
FOR(i,2,N) rk[sa[i]] = (tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w]) ? p : ++p;

去重的原理和上面的类似:如果前半段和后半段在上一轮的rkrk相同的话那它们当前就是相同的。

然后就做完了。。。。

现在我们要解决的是找到一种高效的排序方法,首先显然你不能用

排序,要不然和暴力就没区别了(可能还更慢)。

所以我们考虑

的基数排序。

先放一下代码:

inline void sort(){FOR(i,0,M) tax[i] = 0;FOR(i,1,N) tax[rk[i]]++;FOR(i,1,M) tax[i] += tax[i-1];ROF(i,N,1) sa[tax[rk[tp[i]]]--] = tp[i];
}

首先我们把桶清空,然后统计每种

的出现次数,然后做个前缀和(有助于快速查询排名)。

然后我们重点关注最后一句是在干什么。首先我们按第二关键字从大到小枚举(

),然后我们在找一下当前枚举的这个后缀的第一关键字排名(
​​),然后由于是第二关键字从大到小枚举,所以目前的字符串排名一定是前半段相同的后缀中(当前
相同)最靠后的一个后缀,所以我们的前缀和就派上用场了。最后直接更新一下就可以了(排名是
的后缀是
号后缀)。

是不是很好理解?这样后缀数组就写完了。

附上Luogu - 后缀排序的代码:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>#define Re register
#define LL long long
#define U unsigned
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define SFOR(i,a,b,c) for(Re int i = a;i <= b;i+=c)
#define SROF(i,a,b,c) for(Re int i = a;i >= b;i-=c)
#define CLR(i,a) memset(i,a,sizeof(i))
#define BR printf("--------------------n")
#define DEBUG(x) std::cerr << #x << '=' << x << std::endlconst int MAXN = 1000000+5;char str[MAXN];
int N,sa[MAXN],tax[MAXN],M;
int pool1[MAXN],*rk = pool1,pool2[MAXN],*tp = pool2;inline void sort(){FOR(i,0,M) tax[i] = 0;FOR(i,1,N) tax[rk[i]]++;FOR(i,1,M) tax[i] += tax[i-1];ROF(i,N,1) sa[tax[rk[tp[i]]]--] = tp[i];
}inline void SuffixSort(){M = 75;FOR(i,1,N) rk[i] = str[i]-'0'+1,tp[i] = i;sort();for(int w = 1,p = 0;p < N;w <<= 1,M = p){p = 0;FOR(i,1,w) tp[++p] = N-w+i;FOR(i,1,N) if(sa[i] > w) tp[++p] = sa[i]-w;sort();std::swap(tp,rk);rk[sa[1]] = p = 1;FOR(i,2,N) rk[sa[i]] = (tp[sa[i-1]] == tp[sa[i]] && tp[sa[i-1]+w] == tp[sa[i]+w]) ? p : ++p;}FOR(i,1,N) printf("%d ",sa[i]);
}int main(){scanf("%s",str+1);N = strlen(str+1);SuffixSort();return 0;
}

Height 数组

后缀数组如果只能排序的话那貌似没什么用,大多数后缀数组题目主要还是考察 Height 数组的性质。

首先扔一些定义。。。

表示
号后缀,
表示后缀
的最长公共前缀。同时我们继续沿用上文的
等定义。

定义

如果直接枚举后缀那还求后缀数组干嘛,所以说我们要考虑能否通过后缀数组求出的信息来线性推出

对于

数组有性质
。证明就不证了(笔者太菜不会,其实分类讨论一下就可以了)。

代码:

inline void get(){int j,k = 0;FOR(i,1,N){if(k) --k;int j = sa[rk[i]-1];while(str[i+k] == str[j+k]) ++k;height[rk[i]] = k;}
}

经典应用

求任意后缀的最大 lcp

这东西随便维护一个区间极值就可以了,

查询。

可重叠最长重复子串

意思是求最长的子串使得在字符串中重复出现过。

根据定义就是 height 数组中的最大值

不可重叠最长重复子串

POJ - 1743
我们来考虑二分答案。假设现在我们二分的答案是

,想判断是否有
的符合题目要求的子串。

然后考虑在一些地方将 height 分开,保证每一段最小

。分完组之后枚举一下看看长度是否满足存在不交的重复子串就可以了。

本质不同的子串数量

注意到子串 = 后缀的前缀。

对于排名为

的后缀,它有
个前缀,但是有
个是和排名为
的后缀的前缀相同的,减去就可以了。

所以答案就是

查看作者更多博客:https://blog.nowcoder.net/rainair

c++ 字符串数组长度排序_数组 | 后缀数组的求法及应用相关推荐

  1. php读取数组长度,PHP count():获取数组长度

    PHP count() 函数用来获取数组长度,也即计算数组元素的个数.另外,count() 函数还可以统计对象中的属性个数. count() 语法如下: int count ( mixed $arr ...

  2. java 数组对象长度_Java中的数组长度:关于Java中数组长度的所有信息

    Java中的数组可以包含多个元素,这取决于对象是如何创建的.为了让用户执行不同的操作,必须知道数组的长度.这篇关于"Java中的数组长度"的文章旨在让我们熟悉用于获取数组长度的操 ...

  3. 数组-接口2-参数为一个整型数组和数组长度的整数(该数组输入和运算结果),再加一个整数;预期结果是一个整型数组

    测试接口的代码说明 适用接口:参数为一个整型数组和数组长度的整数(该数组输入和运算结果),再加一个整数:预期结果是一个整型数组 测试接口的具体代码如下: 注意红色字体,用后续小节中的接口进行替换,进行 ...

  4. 二维字符数组按长度排序_字符串长度 字符数组长度

    1.不带转义字符的字符 如:"abc!x=/",其长度为 7 2.带转义字符的字符串 (1) 字符串"abcn":其中的'n'为转义字符(换行符),计算字符串长 ...

  5. 字符串系列(二)——“万金油”后缀数组

    学习后缀数组有感 (原创,转载请注明出处) 在做一些串的问题时,因为其本身处理比较麻烦,光是比较就要耗费O(n²)的复杂度.因此我们使用后缀数组来进行复杂度的简化. 首先要明确,后缀数组是一种工具,可 ...

  6. cf244D. Match amp; Catch 字符串hash (模板)或 后缀数组。。。

    D. Match & Catch 能够用各种方法做.字符串hash.后缀数组,dp.拓展kmp,字典树.. . 字符串hash(模板) http://blog.csdn.net/gdujian ...

  7. java对数组进行排序_用Java对数组进行排序所需的最少交换

    java对数组进行排序 Problem: 问题: In this problem, we would have an unordered array with consecutive distinct ...

  8. [转载] java中对数组进行排序_如何在Java中对数组排序

    参考链接: Java中main()的有效变体 java中对数组进行排序 Java Array is like a container that can hold a fixed number of t ...

  9. java中对数组进行排序_如何在Java中对数组排序

    java中对数组进行排序 Java Array is like a container that can hold a fixed number of the same type of items, ...

最新文章

  1. 第十八章 33用重载输出运算符函数实现字符串的输出
  2. 使用Skywalking实现全链路监
  3. oracle dbms overflow,Oracle DBA课程系列笔记(12_1)
  4. ECMALL数据库关系模型的实现
  5. yolo算法_吴恩达深度学习笔记(100)-目标检测之YOLO 算法讲解
  6. E 速度即转发(牛客挑战赛48)(树套树)
  7. 架构设计 | 分布式体系下,服务分层监控策略
  8. Kaldi 语音识别基础教程
  9. 专利基本知识及撰写要求
  10. 万网域名证书如何查询下载_备案域名证书获取
  11. 自己的作品界面---类似360杀毒软件的界面
  12. 【动态规划的方法论】
  13. Python之pip:pip包管理工具的简介、安装、使用方法之详细攻略
  14. Mysql Where条件执行顺序是从左到右
  15. linux终端实现骇客帝国的字符雨动画
  16. luogu P1600 天天爱跑步
  17. C语言编程规范学习笔记和总结(附华为编程规范机试参考试题)
  18. Java代码一键生成神器,支持Jpa/Mybatis/plus多种ORM框架,亲测好用
  19. abaqus算界面脱粘_Abaqus 粘聚力模型(Cohesive Model)
  20. pcl::lineToLineSegment() 计算空间直线的交点和最小公垂线

热门文章

  1. 软件测试术语中英文对照(部分)
  2. 【Leetcode】刷题题单记录
  3. Web 趋势榜:上周最热门、又实用的 10 大 Web 项目 - 210813
  4. reactjs组件的props属性及其特点
  5. Go支持自定义数据类型:使用type来定义,类似于数据类型的一个别名
  6. 开源的视频笔记合集: 陌溪 / LearningNotes
  7. Scala伴生类与伴生对象:apply方法/伴生对象可以访问伴生类的私有属性
  8. Hadoop SSH免密登录公钥生成并实现不同主机间的免密登录
  9. Linux零拷贝的原理
  10. 【软考-软件设计师】解释程序实现高级语言的三种方式