FAST SUCCINCT TRIES是作者提出来的一种对Trie树进行编码的方式,可以减小该树在内存中空间,同时保留了查询的能力。因为这种方式是基于LOUDS(Level-Ordered Unary Degree Sequence)提出来的,所以需要先了解LOUDS的编码规则:
从根节点开始,按广度优先的方式去遍历这棵树。
扫描到一个节点时,该节点有n个孩子,则用n个1和一个0对这个节点进行编码。
举例如下图所示:从根节点开始依次层序遍历这棵树,遇到一个节点,该节点有几个孩子,就用几个1再加上一个结束标志0对该节点编码,例如,对于节点3,它有3个孩子,就用“1110”对该节点编码。
对这棵树编码完成后得到的是一组01字符串,现在要根据这个字符串来访问树中的节点,可以总结成两个经典的操作:
通过父亲节点找孩子节点
通过孩子节点找父亲节点
为了能够实现上述两个操作从而实现访问树中的任意节点,根据该树的编码特点以及字符串的形式,定义了四个操作:
rank1(i) : 返回在 [0, i] 位置区间内 1 的个数
rank0(i) : 返回在 [0, i] 位置区间内 0 的个数
select1(i) : 返回第i个1的位置(整个bit序列)
select0(i) : 返回第i个0的位置(整个bit序列)
上面的操作可以通过下面的表格来具体详细解释,其中value行的比特序列是上面那张图中的编码序列:
现在基于下面三个公式来访问整个树:
求层序遍历的第i个节点在比特序列中的位置
position(i-th node) = select0(i) +1 //因为节点间编码以0间隔开,所以当前序列位置前面有多少0,就表示有多少节点,第i个节点的位置,前面有i个节点(节点序号从0开始),及定位到第i个0,就可以定位到第i-1个节点编码序列最后一个比特在比特序列的位置,加1后就表示第i个节点的起始位置了。即可知,对任何一个位置来说,开始位置到该点之间的bit 0出现的个数表示该点前面有多少个节点。
求在比特序列中起始位置为p的节点的孩子位置
first-child(i) = select0(rank1(p)) + 1 //因为每一个节点都会通过1的个数去标记其直接孩子的个数,根据这个特性,对任何一个位置,开始位置到该点之间的bit 1出现的个数表示该点前面的节点加上其直接孩子的节点数目。
求在比特序列中起始位置为p的节点的父亲位置
parent(i) = select1(rank0(p)) //这个关系可以根据父亲求孩子来倒推。先求出该节点前面有多少节点,然后根据“对任何一个位置,开始位置到该点之间的bit 1出现的个数表示该点前面的节点加上其直接孩子的节点数目”。倒推父亲节点。
举例如下:
求第4个节点在编码序列中的位置:select0(4)+1 = 11 + 1 =12
求在比特序列中起始位置为12的孩子位置:select0(rank1(12)) = select0(9)+1 = 22
求在比特序列中起始位置为22的节点的父亲位置:select1(rank0(22)) = select1(9) = 12
FST编码基于LOUDS编码方式, FST( FAST SUCCINCT TRIES)对LOUDS进行了进一步压缩, 下图介绍了基本的压缩方法:
FST将LOUDS分成了两层, 上层节点数量少,数据访问频繁, 使用LOUDS-Dense编码方式, 下层节点数多, 数据访问次数少,使用LOUDS-Sparse编码方式.
LOUDS-Dense和LOUDS-Sparse1. LOUDS-Dense
每个节点最多有256个子节点, 那么在LOUDS-Dense编码方式中, 每个节点使用3个256个bit的bit map和一个bit序列来保存信息. 它们分别是: