本人可以说是菜鸟一枚,最近在学习字符串算法,学到后缀数组。从其中涉及的基本定义,到构造后缀数组方法中的其中一种倍增算法,光这些知识,就弄得我云里雾里,前前后后看了有三四天。看了很多大神的博客,提取并进行了一些总结,为加深记忆,在这里写下来,以便以后理解。若有理解不对之处,欢迎各位大神指正。
1、学习后缀数组,最先要明确的几个基本概念就是:子串、后缀、字符串的大小比较、后缀数组、名词数组这几个概念。(概念含义的描述基本都是大同小异了)
子串:很好理解,就是在原字符串中任意截取的一段字符串。加符号描述为:字符串S的子串为r[ i…j ],(i和j分别表示子串在原字符串中开始和结束的位置,其中i ≤ j),
即r[ i…j ]表示的就是i到j这一段S[i],S[i+1],...,S[j]顺次排列形成的字符串。
后缀:一个字符串的后缀,就是指从该字符串中的某个位置i开始,到整个串末尾结束的这样的子字符串。比如字符串”aabaaaab”,这个字符串的后缀有:”aabaaaab”, “abaaaab”, “baaaab”, “aaaab”, “aaab”, “aab”, “ab”, “b”. 通常,我们描述字符串S从i位置开头的后缀表示为Suffix(S,i),即Suffix(S,i)=r[i…len(S)]。
字符串的大小比较:就是将两个字符串从头开始依次按字典序比较。如对于字符串u和v,令i从1开始顺次比较u[i]和v[i](实际编码时从0位置开始), 如果u[i]=v[i],则i+1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v(也就是v<u),比较结束。如果i>len(u)或者i>len(v)仍比较不出结果,那么若len(u)<len(v)则认为u<v ,若len(u)=len(v) 则认为u=v ,若len(u)>len(v)则u>v。
为了方便处理长度不等的后缀之间的大小比较,我们通常约定给字符串的末尾添加一个特殊字符,这个特殊字符没有在字符串中出现过,而且比字符串中任何一个字符都要小。这样当两个不等长的后缀进行比较时,走到较短的后缀的最后一位时,大小自然可以比较出来。另外,对于约定的字符串S,从位置i开头的后缀直接写成Suffix(i),省去Suffix(S,i)中的参数S。
后缀数组:后缀数组SA就是一个一维数组,其元素是SA[0],SA[1]……SA[n-1],保证suffix(SA[i]) < Suffix(SA[i+1]) , 0 ≤ i<n-1。意思就是,首先我们将字符串S的所有n个后缀从小到大排序,我们将排序后的每个后缀的开始位置依次放在SA[0],SA[1]……SA[n-1]中。
名次数组:名次数组Rank保存的是某一后缀在所有后缀中的排名(从小到大)。Rank[i]表示Suffix(i)在所有后缀中的排名。名次数组和后缀数组互为逆运算。有句话描述得很形象:后缀数组(SA)是 “ 排第几的是谁? ” ,名次数组(RANK)是 “你排第几? ”
2、基本概念了解完毕,下面开始了解构造后缀数组的方法。
构造后缀数组可采用倍增算法(Doubling Algorithm)或DC3算法(Difference Cover mod 3).其中,倍增算法相对于DC3算法更好理解,而DC3算法的效率则更高。
我目前只看了倍增算法。
倍增算法,利用各个后缀之间的有机联系,把最坏的时间复杂度降低为O(nlogn)。其核心思想属于递推。
整体思路可以说就是:由每个后缀的前K个字符的大小比较,从而得到前2k个字符的大小比较,依次类推……直至得到所有后缀的比较结果。我们把SAK称为k-后缀数组,其中元素SAK[0], SAK[1]……SAK[n-1]表示的是按照k-前缀(前k个字符)从小到大排序后的每个后缀的开始位置。Rankk含义参考SAK理解。若我们求出SAk和Rankk&#