后缀数组的倍增算法(Prefix Doubling)
最近在自学BWT算法(Burrows-Wheeler transform),其中涉及到对字符串循环移位求编码。直观的办法就是模拟,使用O(n3)的时间求出BWT编码。经过简单的简化后也要O(n2logn)的时间,显然当字符串长度很大时这种方法的效率很低。
由于循环移位的结果类似后缀(二者有所不同,所以在字符串结尾添加了一个字典序严格小于所有字符的符号,例如'\0',使得循环移位的有效部分等同于后缀),因此可以使用后缀树或后缀数组的方式优化BWT的过程。
关于学习倍增算法,你应该:
- 理解朴素的后缀数组生成方法
- 理解基数排序(本文使用了基数排序,至于原始的倍增算法是否是使用基数排序本人也不清楚)
- 了解KMP算法的原理
先来谈谈KMP算法。它之所以能有效减少比对次数是因为它利用了之前比对的结果——利用前缀的自相似性跳过必然失败的匹配,直接进行有可能成功的尝试。
而倍增算法同样拥有类似的思想,例如cake拥有后缀
cake
ake
ke
e
当我们比较了每个后缀第一个字母后(2nd 1st 4th 3rd),实际上我们也知道了每个后缀的第二个字母的比较结果(1st 4th 3rd -)。类似的,后续结果也就知道了。因此,我们可以得到逐步扩展每个后缀的前缀比较结果(2 1 4 3)->(21 14 43 3-)->(214 143 43- 3--)……参考图1(本例和图中所示不同,但思路是一样的)
上述延伸过程是线性增加的。若是再贪心一点,则可以利用上一回的比较结果将该回的前缀比较长度增加一倍,即指数级增长。这也就是倍增算法的核心思路。

图1引用自NOCOW
再来谈谈利用基数排序的算法实现。基数排序分为LSD(Least significant digital)和MSD(Most significant digital)两大类。乍一看后缀数组的比较是从高位开始的(p.s. 为什么不从低位开始呢?删除一个整数而不改变相对大小关系很简单,但添加一个整数而不改变相对大小关系比较麻烦),很适合MSD。但MSD的时间开销随序列复杂度和长度增长很快,仅适用于短序列,所以LSD是个无奈之选。可以说,倍增算法的代码之所以晦涩很大一部分原因就是使用LSD的缘故。
code in C++
#include<stdio.h>
#include<string.h>
#define rank r_sa
const int MAXN=21;
char str[MAXN];
int sa[MAXN];//suffix array
int l_sa[MAXN];//low of sa
int r_sa[MAXN];//reverse mapping of sa, also known as rank array
int t_r_sa[MAXN];//temperary copy of r_sa
char BWT[MAXN];
int c[MAXN+128];//数组长度必须大于字符串长度和字符总数的最大值
bool sa_cmp(int *r,int sa1,int sa2,int j){
//此处完美地进行了越界判断
return (r[sa1]==r[sa2] && r[sa1+j]==r[sa2+j]);
}
int prefixdouble(char *s,int l){
int i,j,k,m;
//对后缀的第一位进行基数排序
memset(c,0,sizeof(c));
for(i=0;i<l;i++)
c[ s[i] ]++;
for(m=1;m<MAXN+128;m++)
c[m]+=c[m-1];
for(i=l-1;i>=0;i--)
sa[ --c[s[i]] ]=i;
//r_sa[i]=k 即第i个后缀排名第k
for(i=0;i<l;i++)
r_sa[i]=s[i];//此时仅需反映相对大小顺序
int p;
for(j=1;j<=l;j*=2){
//由于采用LSD,先对低位进行排序
p=0;
//l_sa[k]=i 即排名第k的是第i个后缀
for(i=l-j;i<l;i++)
l_sa[p++]=i;//长度小于j的后缀无低位关键字,直接排在最前
for(k=0;k<l;k++)
if(sa[k]>=j) l_sa[p++]=sa[k]-j;//第i-j个后缀的低位关键字等于第i个后缀的高位关键字,并且高位关键字在之前已有序
//再对高位进行排序
memset(c,0,sizeof(c));
for(k=0;k<l;k++)
c[ r_sa[ l_sa[k] ] ]++;
for(m=1;m<MAXN+128;m++)
c[m]+=c[m-1];
for(k=l-1;k>=0;k--)
sa[ --c[ r_sa[ l_sa[k] ] ] ]=l_sa[k];
//更新r_sa
memcpy(t_r_sa,r_sa,4*MAXN);
r_sa[ sa[0] ]=p=0;
//相邻后缀如果前缀相同,那么其rank也相同
for(k=1;k<l;k++)
r_sa[sa[k]]=sa_cmp(t_r_sa,sa[k-1],sa[k],j)?p:++p;
if(p==l-1) break;
}
/*test
for(k=0;k<l;k++)
printf("%2d:%s\n",k,s+sa[k]);
BWT[0]=s[l-2];
for(i=1;i<l;i++)
BWT[i]=s[sa[i]-1];
BWT[l]='\0';
printf("trans:");
for(i=0;i<l;i++)
printf("%c",BWT[i]);
*/
}
int main(){
printf("The string inputed should short than 20 symbols.\n");
scanf("%s",str);
int l=strlen(str);
prefixdouble(str,l+1);
for(int i=0;i<l;i++)
printf("%d ",sa[i]);
return 0;
}
后缀数组的倍增算法(Prefix Doubling)的更多相关文章
- 后缀数组:倍增法和DC3的简单理解
一些定义:设字符串S的长度为n,S[0~n-1]. 子串:设0<=i<=j<=n-1,那么由S的第i到第j个字符组成的串为它的子串S[i,j]. 后缀:设0<=i<=n- ...
- 关于后缀数组的倍增算法和height数组
自己看着大牛的论文学了一下后缀数组,看了好久好久,想了好久好久才懂了一点点皮毛TAT 然后就去刷传说中的后缀数组神题,poj3693是进化版的,需要那个相同情况下字典序最小,搞这个搞了超久的说. 先简 ...
- 【HDOJ6223】Infinite Fraction Path(后缀数组,倍增)
题意: 给一个长度为n的字符串s[0..n-1],但i的后继不再是i+1,而是(i*i+1)%n,求所有长度为n的“子串”中,字典序最大的是谁 n<=150000,s[i]=0..9 思路:后缀 ...
- 笔试算法题(40):后缀数组 & 后缀树(Suffix Array & Suffix Tree)
议题:后缀数组(Suffix Array) 分析: 后缀树和后缀数组都是处理字符串的有效工具,前者较为常见,但后者更容易编程实现,空间耗用更少:后缀数组可用于解决最长公共子串问题,多模式匹配问题,最长 ...
- 后缀树 & 后缀数组
后缀树: 字符串匹配算法一般都分为两个步骤,一预处理,二匹配. KMP和AC自动机都是对模式串进行预处理,后缀树和后缀数组则是对文本串进行预处理. 后缀树的性质: 存储所有 n(n-1)/2 个后缀需 ...
- 【后缀数组之SA数组】【真难懂啊】
基本上一搜后缀数组网上的模板都是<后缀数组——处理字符串的有力工具>这一篇的注释,O(nlogn)的复杂度确实很强大,但对于初次接触(比如窝)的人来说理解起来也着实有些困难(比如窝就活活好 ...
- 后缀数组--summer-work之我连模板题都做不起
这章要比上章的AC自动机要难理解. 这里首先要理解基数排序:基数排序与桶排序,计数排序[详解] 下面通过这个积累信心:五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码) 下面认真研读下这篇: [转 ...
- HDU 4691 正解后缀数组(暴力也能过)
本来是个后缀数组,考察算法的中级题目,暴力居然也可以水过,就看你跳不跳坑了(c++和G++返回结果就很不一样,关键看编译器) 丝毫不差的代码,就看运气如何了.唯一差别c++还是G++,但正解是后缀数组 ...
- 利用后缀数组(suffix array)求最长公共子串(longest common substring)
摘要:本文讨论了最长公共子串的的相关算法的时间复杂度,然后在后缀数组的基础上提出了一个时间复杂度为o(n^2*logn),空间复杂度为o(n)的算法.该算法虽然不及动态规划和后缀树算法的复杂度低,但其 ...
随机推荐
- 【.net 深呼吸】细说CodeDom(3):命名空间
在上一篇文章中,老周介绍了表达式和语句,尽管老周没有把所有的内容都讲一遍,但相信大伙至少已经掌握基本用法.在本文中,咱们继续探讨 CodeDom 方面的奥秘,这一次咱们聊聊命名空间. 在开始之前,老周 ...
- LDR详解
ARM指令集中,LDR通常都是作加载指令的,但是它也可以作伪指令. LDR伪指令的形式是"LDR Rn,=expr".下面举一个例子来说明它的用法. COUNT EQU ...
- 2.WindowsServer2012R2装完的一些友好化设置
网站部署之~Windows Server | 本地部署 http://www.cnblogs.com/dunitian/p/4822808.html#iis 1.桌面图标(控制面板里面屏蔽了,得自己输 ...
- H5程序员如何利用cordova开发跨平台应用
什么是Cordova? Cordova以前也叫PhoneGap,它提供了一组设备相关的API,通过这组API,移动应用能够以JavaScript访问原生的设备功能,如摄像头.麦克风等.Cordova还 ...
- PHP设计模式(四)单例模式(Singleton For PHP)
今天讲单例设计模式,这种设计模式和工厂模式一样,用的非常非常多,同时单例模式比较容易的一种设计模式. 一.什么是单例设计模式 单例模式,也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对 ...
- 微信小程序开发—快速掌握组件及API的方法
微信小程序框架为开发者提供了一系列的组件和API接口. 组件主要完成小程序的视图部分,例如文字.图片显示.API主要完成逻辑功能,例如网络请求.数据存储.音视频播放控制,以及微信开放的微信登录.微信支 ...
- 编译器开发系列--Ocelot语言4.类型定义的检查
这里主要介绍一下检查循环定义的结构体.联合体.是对成员中包含自己本身的结构体.联合体进行检查.所谓"成员中包含自己本身",举例来说,就是指下面这样的定义. struct point ...
- github免输用户名/密码SSH登录的配置
从github上获取的,自己整理了下,以备后用. Generating an SSH key mac windows SSH keys are a way to identify trusted co ...
- [bzoj2152][聪聪和可可] (点分治+概率)
Description 聪聪和可可是兄弟俩,他们俩经常为了一些琐事打起来,例如家中只剩下最后一根冰棍而两人都想吃.两个人都想玩儿电脑(可是他们家只有一台电脑)……遇到这种问题,一般情况下石头剪刀布就好 ...
- 喜马拉雅音频下载器 V1.2 支持专辑批量下载 喜马拉雅mp3下载导出 喜马拉雅下载器
出差一个星期回来看邮箱,发现好多网友评论说网站又更新了,老程序不能用了,有热心网友放出修改版的,我就不重复造车了..大家可以去这里下载 http://blog.csdn.net/suqingheang ...