在前面的博文里面分析了SA数组和rank数组的实现过程,实际上也就是倍增算法的思想分析!虽然思想上面懂了,但是代码实现还是很难理解的!因为代码里面做了太多的优化。
整个代码的实现可以分成两部分:1、对所有后缀的第一次排序!运用第一次排序的结果来推算后面的排序!
一、对所有后缀的第一次排序:
- for (i = 0; i < m; i++) ws[i] = 0;
- for (i = 0; i < n; i++) ws[x[i] = r[i]]++;
- for (i = 1; i < m; i++) ws[i] += ws[i - 1];
- for (i = n - 1; i >= 0; i--) sa[--ws[x[i]]] = i;
这里用到了x和ws两个辅助数组!一般而言,用一个辅助数组就很难理解了,作者竟然用了两个! 这主要是因为:我们通常用到的排序是value的排序,我们是不理会下标的。而这里是对value排序,但是输出的结果却是下标!所以,这里的排序又变得复杂了。我们不仅要考虑value,还要考虑index,我觉得这才是真正难以理解后缀数组的地方。
这里的排序实际上是用的是,我觉得类似于Hash的方式。把字符串r数组的value映射到辅助数组ws的index。这样的话实际已经排序了,但是我们SA数组存储的是下标,这里才是难点!该怎么做呢?我们知道ws里面存储的其实是r字符串中各个字符的个数。这里我们转化一下,转化成每个字符的排名:比如
假设:ws中是这样存储的:
index: 65 66 67 68 69
value: 1 2 1 1 2
其实就是:字符串中字符及个数分别是:
A:1 B:2 C:1 D:1 E:2
转化成排名:( for (i = 1; i < m; i++) ws[i] += ws[i - 1]; )
则ws变成:
index: 65 66 67 68 69
value: 1 3 4 5 7
这样的话,我们存储下标的时候就可以这样:
for (i = n - 1; i >= 0; i--) sa[--ws[x[i]]] = i;
由于下标从0开始,而且可能相同的字符不止一个,故需要--ws[x[i]]; 就用原字符串逆序的方式把排序后后缀下标存储到sa数组了!
个人感觉计算过程真的是挺精巧的!里面包含的思想,真的值得我深入学习!大牛就是大牛!
由于x数组已经类似于计算出排名了,所以我们不需要真正去计算了,如果需要计算的话:把里面的值减一个最小值然后加1就可以了!
总的来说,只考虑后缀首字母的话,对后缀排序还是比较简单的!更难的在下面用倍增的思想对整个后缀排序的过程。这才是倍增算法的真正核心!上面充其量是预处理罢了!