相信很多初学后缀数组的ACMer在学习蓝书中的后缀数组部分遇到了一些障碍,可能像我一样看明白了P219 ——220的讲解和算法,百度了基数排序的方法,然后被卡在P221的代码上了,本文目的即分享我对这段代码的理解。
首先明确其中每个变量的含义,n为字符串s的长度,m为字符串中可能包含的最大字符值+1(故一开始应置为'z' + 1),sa数组存的是P220四幅图中每一幅结束时最下面一行的最终排名对应情况(!!“排名对应情况”看不懂下面还有解释),y数组存的是后三幅图中二元组的第二元的排名对应情况(第一幅没有第二元),x[i]表示第i种字符/二元组对应的编号,c[i]表示第0种到第i种字符/二元组的总数量。
先解释清楚排名对应情况的含义,以P220第二幅图为例,最终排名结果为1 2 4 1 1 1 2 3 ,那么他的排名对应情况为0 3 4 5 1 6 7 2,这是怎么对应的呢?就是先把排名第1的位置下标放在最前面,也就是0 3 4 5,然后再把排第2的下标放前面,就是1 6 ,然后把排第三......
下面只以k第一次循环时的情况说明整段代码。
33 到37行用x数组统计各类字符的种类,用c数组统计每种字符出现的次数,然后用sa数组把他们依次装入,装的时候就是基数排序从木桶里将每一个数拿出来然后放到相应的位置的过程,装完以后sa的内容就是前文所说的第一幅图中最后一行的排名对应情况,此时0 —— 7 七个下标正好在sa中各出现一次。
接下来进入了k的循环,k的含义是什么呢,以第二幅图为例,第二行每个二元组头上都有条竖线和一条斜线,斜线所指的位置比竖线指的大k,对,k就是这个意思,可以代入第三四幅图中,也就是k循环的二三次,来看看是不是这样。
下面要排序二元组的第二关键字,然后把排名装入y数组,我们看第二幅图的中间一行,二元组的第二关键字从左至右依次为 1 2 1 1 1 1 2 0,所以排名对应情况为7 0 2 3 4 5 1 6,那么怎么实现这个排序呢?代码39——41行,为的是把0对应的位置先装进y数组里,接下来42——45,是把剩下的放进y数组。42——45是怎么做到把剩下的放进去的?看P220的第二幅图,第一行的数字就是第一幅图的最后一行数字,而第一幅图最后一行数字的排名对应情况已经放到sa数组里,sa[i] - k表示的是一种对应关系:第二幅图的第一行里第i个数字,对应到第二行的第i - k个数字的第二元,只要明白了这种对应关系,代码为什么这么写,琢磨一下就可以明白了。
第46到第52行是排序第一关键字,这里面比较难理解的是51——52行,首先要倒着循环是因为i大的y[i]是第二元比较大的,所以第一元相同时要先放第二元比较大的,因此要倒着循环。那么第y[i]个数应该放哪呢?从第二幅图中看到,第二行中第y[i]个二元组对应的第一元是第一行中的第y[i]个数,第一行中第y[i]个数的排名就是当前的sa[- -c[x[y[i]]]](为什么?x[y[i]]表示y[i]的种类,c[x[y[i]]]表示放到第x[y[i]]种已经放了多少个,不断- -使得大小相同的x[y[i]]排出先后,所以第y[i]个二元组放在sa里是sa[--c[x[y[i] ] ] ])。
54——58行中,p代表的是不同的二元组的数量,如果p >= n,则build_sa结束,现在sa中已经排完序,所以相等的二元组在sa里只可能相邻,56行为的就是判断有多少相邻且不等的二元组,这样来统计这样排序之后不同的二元组的数量p,59行如果算法未结束将二元组的种类m更新为p。
至此,build_sa部分的代码解释完了,其他部分的代码相对来说要更好理解,先给出补全版的全部代码。
#pragma warning(disable:4786)
#pragma comment(linker, "/STACK:102400000,102400000")
#include
#include
#include
#include
#include
#include
#include