最近在自学BWT算法(Burrows-Wheeler transform),其中涉及到对字符串循环移位求编码。直观的办法就是模拟,使用O(n3)的时间求出BWT编码。经过简单的简化后也要O(n2logn)的时间,显然当字符串长度很大时这种方法的效率很低。

由于循环移位的结果类似后缀(二者有所不同,所以在字符串结尾添加了一个字典序严格小于所有字符的符号,例如'\0',使得循环移位的有效部分等同于后缀),因此可以使用后缀树或后缀数组的方式优化BWT的过程。


关于学习倍增算法,你应该:

  1. 理解朴素的后缀数组生成方法
  2. 理解基数排序(本文使用了基数排序,至于原始的倍增算法是否是使用基数排序本人也不清楚)
  3. 了解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)的更多相关文章

  1. 后缀数组:倍增法和DC3的简单理解

    一些定义:设字符串S的长度为n,S[0~n-1]. 子串:设0<=i<=j<=n-1,那么由S的第i到第j个字符组成的串为它的子串S[i,j]. 后缀:设0<=i<=n- ...

  2. 关于后缀数组的倍增算法和height数组

    自己看着大牛的论文学了一下后缀数组,看了好久好久,想了好久好久才懂了一点点皮毛TAT 然后就去刷传说中的后缀数组神题,poj3693是进化版的,需要那个相同情况下字典序最小,搞这个搞了超久的说. 先简 ...

  3. 【HDOJ6223】Infinite Fraction Path(后缀数组,倍增)

    题意: 给一个长度为n的字符串s[0..n-1],但i的后继不再是i+1,而是(i*i+1)%n,求所有长度为n的“子串”中,字典序最大的是谁 n<=150000,s[i]=0..9 思路:后缀 ...

  4. 笔试算法题(40):后缀数组 & 后缀树(Suffix Array & Suffix Tree)

    议题:后缀数组(Suffix Array) 分析: 后缀树和后缀数组都是处理字符串的有效工具,前者较为常见,但后者更容易编程实现,空间耗用更少:后缀数组可用于解决最长公共子串问题,多模式匹配问题,最长 ...

  5. 后缀树 & 后缀数组

    后缀树: 字符串匹配算法一般都分为两个步骤,一预处理,二匹配. KMP和AC自动机都是对模式串进行预处理,后缀树和后缀数组则是对文本串进行预处理. 后缀树的性质: 存储所有 n(n-1)/2 个后缀需 ...

  6. 【后缀数组之SA数组】【真难懂啊】

    基本上一搜后缀数组网上的模板都是<后缀数组——处理字符串的有力工具>这一篇的注释,O(nlogn)的复杂度确实很强大,但对于初次接触(比如窝)的人来说理解起来也着实有些困难(比如窝就活活好 ...

  7. 后缀数组--summer-work之我连模板题都做不起

    这章要比上章的AC自动机要难理解. 这里首先要理解基数排序:基数排序与桶排序,计数排序[详解] 下面通过这个积累信心:五分钟搞懂后缀数组!后缀数组解析以及应用(附详解代码) 下面认真研读下这篇: [转 ...

  8. HDU 4691 正解后缀数组(暴力也能过)

    本来是个后缀数组,考察算法的中级题目,暴力居然也可以水过,就看你跳不跳坑了(c++和G++返回结果就很不一样,关键看编译器) 丝毫不差的代码,就看运气如何了.唯一差别c++还是G++,但正解是后缀数组 ...

  9. 利用后缀数组(suffix array)求最长公共子串(longest common substring)

    摘要:本文讨论了最长公共子串的的相关算法的时间复杂度,然后在后缀数组的基础上提出了一个时间复杂度为o(n^2*logn),空间复杂度为o(n)的算法.该算法虽然不及动态规划和后缀树算法的复杂度低,但其 ...

随机推荐

  1. C# ini文件操作【源码下载】

    介绍C#如何对ini文件进行读写操作,C#可以通过调用[kernel32.dll]文件中的 WritePrivateProfileString()和GetPrivateProfileString()函 ...

  2. Android业务组件化之URL Scheme使用

    前言: 最近公司业务发展迅速,单一的项目工程不再适合公司发展需要,所以开始推进公司APP业务组件化,很荣幸自己能够牵头做这件事,经过研究实现组件化的通信方案通过URL Scheme,所以想着现在还是在 ...

  3. JavaScript正则表达式,你真的知道?

    一.前言 粗浅的编写正则表达式,是造成性能瓶颈的主要原因.如下: var reg1 = /(A+A+)+B/; var reg2 = /AA+B/; 上述两个正则表达式,匹配效果是一样的,但是,效率就 ...

  4. C#为IE编写BHO插件心得

    啥是BHO,其实大家都用过,没听过只是没在意而已,来张图你就知道是什么了 是不是很熟悉,就是这么个玩意~~ 先说说我要用来干嘛~我们有个库,里面数据很全面,但是某个部门需要在第三方的B/S系统录入某些 ...

  5. Win.ini和注册表的读取写入

    最近在做打包的工作,应用程序的配置信息可以放在注册表文件中,但是在以前的16位操作系统下,配置信息放在Win.ini文件中.下面介绍一下Win.ini文件的读写方法和注册表的编程. 先介绍下Win.i ...

  6. 如何避免git每次提交都输入密码

    在ubuntu系统中,如何避免git每次提交都输入用户名和密码?操作步聚如下:1: cd 回车: 进入当前用户目录下:2: vim .git-credentials (如果没有安装vim 用其它编辑器 ...

  7. Angular源码分析之$compile

    @(Angular) $compile,在Angular中即"编译"服务,它涉及到Angular应用的"编译"和"链接"两个阶段,根据从DO ...

  8. HTML5轻松实现搜索框提示文字点击消失---及placeholder颜色的设置

    在做搜索框的时候无意间发现html5的input里有个placeholder属性能轻松实现提示文字点击消失功能,之前还傻傻的在用js来实现类似功能... 示例 <form action=&quo ...

  9. Three.JS 从世界坐标系转换到屏幕坐标系

    var projector = new THREE.Projector(); var world_vector = new THREE.Vector3(0,0,1); var vector = pro ...

  10. iOS之ProtocolBuffer搭建和示例demo

    这次搭建iOS的ProtocolBuffer编译器和把*.proto源文件编译成*.pbobjc.h 和 *.pbobjc.m文件时,碰到不少问题! 搭建pb编译器到时没有什么问题,只是在把*.pro ...