后缀数组

转自http://www.yhzq-blog.cc/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/

http://www.yhzq-blog.cc/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93/
后缀数组算法总结

后缀数组提供了一种新思想——倍增和字符串的有趣结合

先来接触一些基础定义

子串:就是字符串的一部分,必须连续。
后缀:是一种子串,它的结尾必须为字符串的最后。
大小比较:就是字典序比较,从头开始比,不相同的话字典序大的那个大,假如相同就向后移动。假如移到其中一个串的结尾还相同的话,长的那个大。
后缀数组:把所有的后缀编号,排序后把编号存在这个数组里。
名次数组:存的是每个后缀的名次

求sa有很多的方法,比较常用的是倍增法,能在O(nlogn)

的时间里求出sa。其实还有很多方法,但是比较难实现。

我们使用的例子是“aabaaaab”它的结果如图所示:

images

我们先把它的所有后缀都列出来:

images

如何排序呢,最好想的就是万能的sort。但是效率太低了,是O(n2logn)
的,别忘了比较两个字符串也是O(n)

的。

这样的话我们就需要用到某种O(n)

的排序类型了。于是我们想到了计数排序

何为计数排序呢,

一般的排序都需要比较其中元素的值,这种排序的复杂度下限就是O(nlogn)

,算导上有证明,但是计数排序不需要比较,假如输入一个数X,我们可以确定小于X的元素的个数,这样,就可以把这个数放在输出数组的指定位置上。

步骤:

1、初始化辅助数组c[i]。

2、遍历每一个输入元素,如果一个输入元素为i,则辅助数组中相应的c[i]的值加1。执行完毕之后。数组c中存储的就是各个键值在输入数组中出现的次数。

3、再计算一下前缀和,我们就知道了小于每个数的个数了。

4、将a[i]放到它在输出数组的正确位置上。对于一个值来说,c[a[i]]的值就是它在输出数组中的正确位置。在做这步的时候,把c[i]减1,这样就能处理相同值的情况了。

计数排序的时间复杂度就为O(n)

了。可这有什么用呢,我们是字典序排序。

这时候,我们就可以使用倍增。因为倍增的话,每次的关键字长度都会变成它原来的两倍,而在后缀中,满足在同一个字符串中的性质,它其中有很多重叠的部分。实际上,我们可以把倍增出来的关键字变成两份,一份是上次关键字排序出来的数组,因为有重复的元素,所以对于另一半来说,它们一定是上一次排出来序的字符串的某个后缀!因为上一次已经让关键字有序了,所以我们直接把上一次排序的数组后移就可以了!前面留空的就是长度不到关键字长度的串,直接按它们的长度摆上就行了。

现在我们就得到了O(nlogn)

的算法了。

接下来我们一点一点地解释一下关于怎么实现的问题:

这里,

n表示字符串的长度
c是上文所述的辅助数组
x表示rank数组,下标表示关键字的位置,值表示关键字大小(rank),相同的值有相同的rank。初始化为字符串s的每个字符大小(此时x并不代表rank,只借助其值比较相对大小)。在每次迭代后,根据sa重新赋值,并代表rank。其实,我觉得可以这么理解,x存的是每个后缀的前缀的种类。因为一开始前缀的长度为1,所以它存的就是s每个字符的大小。以后将每个前缀有序编号了以后这里存的就是字符串的编号。
y表示排序后的第二关键字数组,下标表示名次,值代表第二关键字的首字符位置。
sa构造完成前表示关键字数组,下标表示名次,值表示关键字的首字符位置,值相同的时候名次根据在原串中相对位置的先后决定;构造完成后表示后缀数组,下标表示名次,值表示后缀的首字符位置。

我们先看一下第一段代码
C++
for (int i=0;i

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值