《后缀数组》笔记

摘自http://www.nocow.cn/index.php/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84

后缀数组sa:将s的n个后缀从小到大排序后将 排序后的后缀的开头位置 顺次放入sa中,则sa[i]储存的是排第i大的后缀的开头位置。简单的记忆就是“排第几的是谁”。

名次数组rank:rank[i]保存的是suffix(i){后缀}在所有后缀中从小到大排列的名次。则 若 sa[i]=j,则 rank[j]=i。简单的记忆就是“你排第几”。

对于 后缀数组sa 与 名次数组rank ,有

rank[ sa[i] ]=i (这是很重要的一点,通过sa与rank的关系可以求出后缀数组)

由此可看出,后缀数组sa 与名次数组rank的关系为互逆关系。

字符串aabaaaab的sa数组与rank数组

倍增算法

一、主要思路:倍增,s[i..i + 2k − 1]的排名通过s[i..i + 2k − 1 − 1]和s[i + 2k − 1..i + 2k − 1]的排名得到。

二、简要过程:已知每个长度为2k − 1的字符串的排名,则可作为每个长度为2k的字符串求排名的关键字xy,s[i..i + 2k − 1]第一关键字x为s[i..i + 2k − 1 − 1]的排名,第二关键字y为s[i + 2k − 1..i + 2k − 1]的排名。以字符串aabaaaab为例:

1. k=0,对每个字符开始的长度为20 = 1的子串进行排序,得到rank[1..8]={1,1,2,1,1,1,1,2}
2. k=1,对每个字符开始的长度为21 = 2的子串进行排序:由k=0的rank得关键字xy[1..8]={11,12,21,11,11,11,12,20},得到rank[1..8]={1,2,4,1,1,1,2,3}
3. k=2,对每个字符开始的长度为22 = 4的子串进行排序:由k=1的rank得关键字xy[1..8]={14,21,41,11,12,13,20,30},得到rank[1..8]={4,6,8,1,2,3,5,7}
4. k=3,对每个字符开始的长度为23 = 8的子串进行排序:由k=2的rank得关键字xy[1..8]={42,63,85,17,20,30,50,70},得到rank[1..8]={4,6,8,1,2,3,5,7}

注意:在排序过程中,rank[]可以有相同排名,但是sa[]排第几是没有相同的(就像Excel的排序,sa相当于编号,rank相当于排名)。这点可以从程序中体现。建议读者跟踪一下程序体会一下。

整个过程如图:


三、时间复杂度分析:每一趟的计数排序的时间复杂度是O(n),排序的次数共log n次,总的时间复杂度为O(n log n)。

算法代码如下:


最长公共前缀

求出了rank和sa数组还不够,通常我们需要由rank与sa数组计算出一个辅助工具height数组——最长公共前缀(LCP)。


height 数组: 定义height[i]=suffix(sa[i-1]) 和 suffix(sa[i]) 的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。

由height数组可得,对于j和k ,不妨设rank[j]<rank[k], 则有以下性质:suffix(j) 和 suffix(k) 的最长公共前缀为 height[rank[j]+1],height[rank[j]+2], height[rank[j]+3], … ,height[rank[k]] 中的最小值。
以"aabaaaab"为例,求后缀"abaaaab"和后缀"aaab"的最长公共前缀,如图,可见其最长公共前缀等于1。

所以说,计算最长公共前缀是一个典型的RMQ问题。

如果直接按照sa的顺序一个一个求解,每一次比较最坏的时间复杂度是O(len(s)),一共要比较len(s)次,所以时间复杂度是O(len(s)2)。这样求height数组是非常慢的,而且没有用到之前所说height数组的性质。

那么,如何高效地计算后缀间的最长公共前缀呢?

当然是使用之前所说的性质。定义h[i]为suffix(i)和前一名次后缀的最长公共前缀{sa[ rank[ suffix(i) ]-1 ]}。由性质可得,

h[i] \ge h[i-1]-1
 简单的证明如下:设suffix(k)是排在suffix(i-1)前一位的后缀,则它们的最长公共前缀显然是h[i-1]。那么,suffix(k+1)显然将排在suffix(i)的前面。并且,suffix(k+1)&suffix(i) 相对于 suffix(k)&suffix(i-1)来说就是同时去掉了第一位,即少了一位的匹配数。所以suffix(i)和前一名次后缀的最长公共前缀至少是h[i-1]-1。

显然,我们可以按照h数组的顺序计算height。时间复杂度分析:求一次height后位数-1,一共有len(s)个后缀,所以只能退len(s)次,也就是说,求解的时间复杂度是O(len(s))。

算法代码如下:


后缀数组的应用

先提出后缀数组的几种常用技巧:

  1. 建议多找找与height数组的关联。
  2. 将几个字符串贴在一起,用特殊符号间隔开:如aab与aaab,可合并成aab$aaab。
  3. 二分+分组(思想)的方法:枚举出答案后,就能将合法的情况划分到一个组判断。

以下列举出几个后缀数组的应用供大家思考,也可在讨论页讨论。

  1. 求两个后缀的最长公共前缀
  2. 求字符串的可重叠的最长重复子串:如ababa可重叠的最长重复子串是aba
  3. 求字符串的不可重叠的最长重复子串:如ababa不可重叠的最长重复子串是ab [提示:想想建议3]
  4. 计算不相同子串的个数:如aaaa的不相同子串数是4
  5. 计算最长回文子串:如aabaaaab的最长回文子串是6(baaaab)。[提示:想想建议2]
  6. 求两个字符串的最长公共子串:如aaba与abac的最长公共子串是aba。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值