这两天在学习后缀数组,但是翻阅网上很多博客都没有找到一个比较适合初学者的,于是没有办法,自己参考着-----算法合集之《后缀数组——处理字符串的有力工具》去学习,发了两天时间,虽然没有弄懂,那个倍增法的原理,但是步骤是了解了。为了省去读者的切换,我从作者那边复制我认为重要并且代码讲解需要的部分。如下
1.1基本定义
子串:
字符串S的子串r[i..j],i≤j,表示r串中从i到j这一段,也就是顺次排列r[i],r[i+1],...,r[j]形成的字符串。
后缀:
后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。字符串r的从第i个字符开始的后缀表示为Suffix(i),也就是
Suffix(i)=r[i..len(r)]。(也就是说后缀是特殊的子串 这是我自己加上的)大小比较:
关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u[i]和v[i],
如果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的两个开头位置不同的后缀u和v进行比较的结果不可能是相等,因为u=v的必要条件len(u)=len(v)在这里不可能满足。后缀数组:
后缀数组SA是一个一维数组,它保存1..n的某个排列SA[1],SA[2],……,SA[n],并且保证Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n。也就是将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA中。
名次数组:
名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。容易看出,后缀数组和名次数组为互逆运算。如图1所示。
上面那句话是比较重点 因为到下面讲解时容易混淆。
为了完成后缀数组的学习,我们还要学习基数排序,基数排序有LSD与MSD 后缀树组只要学习LSD 为什么强调这点,因为之前看代码不懂,以为是MSD的代码,结果去学MSD,但是网上也没有比较好的MSD(我没有找到,不能说没有),为了方便这里写出LSD的排序方法(百度百科)。但是我认为这个例子足够掌握LSD了。
假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
如果大家还是有点不是太懂,我们这样看
43 | |||||||||
93 | 65 | ||||||||
81 | 22 | 73 | 14 | 55 | 28 | 39 |
0 1 2 3 4 5 6 7 8 9
把0到9想象成一个队列,先进先出,第一次比较个位,结尾是1的 就放在1号这个队列里,依次放入然后按从小到大取出得到新的数组81, 22, 73, 93, 43, 14, 55, 65, 28, 39 对于3号队列里放了3个所以,先进先出,73最开始进去的,所以第一个拿出来,第二部,以这个最新的数组再次放入,这次按十位这个位的数字放入。
好了,你已经学会了基数排序,然后让我们开始着手后缀数组吧。
要想实现后缀数组,最重要的就在这个图上,
rank数组就是上面提到的名词数组,那么怎么求呢?
那个图就是步骤,让我解释一下原图的意思,第一次排序就是讲原来的字母按照大小比较生成rank数组,然后将rank数组组合成一个2位十进制
也就是11 12 21 11 11 11 11 12 20 就是下面想 x y那一栏 然后比较这些十位数的大小 又生成一个rank[]数组,然后重复,什么时候结束呢,当我们发现rank[]数组没有重复排名的时候就停止,大家,这里不要想着rank的含义,就想着他是一个普通的一位数。
下面就是比较核心的了,怎么生成rank[]数组,就是基数排序
也许你会问快排不行吗,当然可以,只是这里就是俩位数,用基数排序,时间复杂度更低,为什么?快排的时间复杂度位n*logn 而基数排序的时间复杂度为O(n) 最坏情况下,排序次数为logn次,所以快排总的时间复杂度为O(nlogn*logn)。基数排序的时间复杂度为n*logn
具体证明这里就不写了,当然我在写这篇博客的时候是不会证明的,但是论文上是这样说的。
正题:
首先 对数组 a a b a a a a b进行排序
第一步 先将第一个a加入进来
a |
a b c d e f g
第二步 先将第二个a加入进来 这里为了区分 不同a 所以用字母在数组中的下标来表示
1 | |||||||||
0 |
a b c d e f g
第三步 将下标为2的b加入进来 (数组下标从0开始)
1 | |||||||||
0 | 2 |
a b c d e f g
第四步 将下标为3的a加入进来 (数组下标从0开始)
3 | |||||||||
1 | |||||||||
0 | 2 |
a b c d e f g
依次进行,这里就省略了
直接到最后一步
6 | ||||
5 | ||||
4 | ||||
3 | ||||
1 | 7 | |||
0 | 2 |
a b c d e
然后取出 先从小的取(小的指a b c d e 也就是分割线下面的 a b c d e)
a最小并且有数据,从a取,这么多,怎么取,先进先出,最底下的最开始放进去,那么他也最开始拿出来,依次是
0 1 3 4 5 6 2 7 (这里是拿出来的下标)
那么rank怎么得到呢,相必大家都看出来了,在一列的是一个等级,也就是说 (0 1 3 4 5 6)这个是相等的等级那么至于等级
从0开始表示最大还是从1开始表示最大,随便你
那么这里就要知道rank[]数组的含义了,rank[i]表示在原数组,也就是要排序的数组,下标为i的元素的排名,相等元素排名一样
所以得到rank[0] 代表(a a b a a a a b)第0个下标所代表的元素的排名,因为下标为0的a 在第一列中所以我设为1
rank[0] = 1
下面考察下标为2的b(a a b a a a a b) 因为下标为 2 的b在第二列中 所以我排名为2.
所以会得到
rank数组为{ 1 1 2 1 1 1 1 2} 如图片所示。待续。。。。。。