形式陈述
长度为 、含 个1的位串有 种。若仍把每位原样保存,已经用了 位,再精简查询目录也无法省掉这部分。RRR把每个小块拆成“有多少个1”与“这些1怎样排列”两份信息,以二项式系数理路二项式系数Binomial coefficientn 元集合的 k 元子集数,记作 C(n,k)。给出类内编码的精确范围。[1, §4.1]
本页输入静态位串 。查询沿用rank/select理路Rank 与 Select 查询Rank and select在位串上计算前缀频数或定位第 j 次出现,并固定端点和索引约定。:rank(i)数半开前缀 的1,;select(k)返回第 个1的零基位置, 从1起,不存在返回 。编码同时保留access,允许空串、全零、全一和不足一整块的末块。
类别和可逆偏移
取块宽 ,第 块实际长度为 。类别 是该块1的个数。若1的位置为
定义偏移
这里采用colex次序:比较两个位置集合时,最大不同位置属于哪一边,哪一边就排后。它不是把左到右显示的二进制文本按普通字典序排序。类别固定后, 恰好无遗漏地编号全部 种排列。
类别紧存为 位;偏移用
位。 或 时只有一种排列,,偏移不占位。若组合数不是2的幂,尚有未使用的二进制码,解码器必须拒绝 。末块按真实 处理,不把补入的零算进原位串。
同时保存两种前缀
偏移字段长短不一,不能用 乘固定宽度找到第 个字段。定义块前累计1数和偏移起点
每 块组成一大块。在大块起点存全局 ;每个小块存相对本大块起点的两个差值。全局字段各用 位,因为总1数和总偏移位数均不超过 。局部字段各用 位。
两个目录作用不同: 给rank已跨过的1数, 给压缩数据的物理位置。类别给出 后,从 读取偏移,便能解出本块。查询 时令 ,把 与块内前 位的1数相加; 直接返回头中总数 。
直觉
八位全零和八位全一各只有一种形状。类别已经把形状说完,再存八位内容没有新增信息。八位恰有四个1时却有70种形状,类内偏移需要七位。压缩效果取决于每块所属类别,而非只把所有块换成同一个较短整数。
不等长编码省下内容位,也打破了固定宽数组的直接寻址。两级目录把“此前累计多少位”压成少量全局数和较小的局部数;类内微表则把解码工作限制在一个小块。空间节省和快速定位分别由这两步承担。
类别、偏移与两种前缀目录
例子与边界
四块怎样变成十位偏移
取 ,位串为
text00000000 11111111 10101010 00010000
1
第三块的1在块内位置 ,故
| 块下标 |
类别 |
偏移 |
位宽 |
|
| 0 |
0 |
0 |
0 |
(0,0) |
| 1 |
8 |
0 |
0 |
(0,0) |
| 2 |
4 |
20 |
7 |
(8,0) |
| 3 |
1 |
3 |
3 |
(12,7) |
偏移串是 0010100 011,共10位;类别另用 位。计算rank(21)时,前两块共有8个1,第三块的前五位 10101 有3个1,因此得到11。第11个1位于原串下标20;第14个1不存在。
这不是一份“总共10位”的文件。参考程序的头为正整数 的gamma码,再加总1数和偏移总长。gamma码把一个正整数的二进制表示前置“位长减一”个零,因此长度为 。本例头33位,类别16位,全局目录24位,局部目录40位,偏移10位,共123位;最后补五个零对齐到16字节。小例为了可手算,目录反而使总长超过原始32位。
端点不能从公式中漏掉
块宽8有九个可能类别,故需要四位类别码;写成 会无法表示全一块的类别8。相反,只有一种类内排列时偏移宽度就是零,不应为了“每块至少有一个字段”额外写一个零位。
若末块长度为3,类别2只有三种排列。两位偏移 11 表示数值3,必须拒绝;按整块宽8解码会错误地把它当成合法排列。修改位串长度而不重建末块类别、目录和偏移,也不能保持原编码有效。
这是静态表示。插入一位会改变后续分块,翻转一位可能改变类别与偏移宽度,后续位流和目录也随之移动。动态维护需要另一个更新结构,不能从静态rank的查询时间推出。
推论与应用
组合秩为什么恰好可逆
固定长度 和1数 。最大1位置为 的集合,其余 个位置来自 ,共有 个。最大位置小于 的集合共有 个,因此当前组恰占从 开始的连续区间。组内再按剩余位置递归排列,就得到前面的求和式。
逆过程从 开始,找最大的 使 ,把位置 置1,再用 解长度 、1数 的问题。这样的 存在,因为 ;由最大性和Pascal恒等式,余数满足
所以每次递归都进入下一层的合法范围,最后唯一到达空集合和偏移0。实现中让位置指针只向左移动,各轮合计检查 个二项式值,不必生成全部位型再搜索。
信息主项与导航冗余分别付账
令块数 。固定各块的1数后,独立选择类内排列有 种;它们只是全体含 个1的长位串的一部分。因此
这只界定偏移负载。类别和目录还需
位。头和字节对齐另计。对足够大的 取 、,这些开销连同 都是 。
在Word-RAM理路Word-RAM 模型Word RAM · Word-RAM model以 w 位机器字、常数时间随机访存和明确字级操作集分析算法的随机访问机模型。上,固定 并提供字内移位、掩码、乘除和随机访存,每个目录字段及块偏移占常数个字。用微块查表理路四俄罗斯方法与字级并行Four Russians method · bit parallelism · broadword programming把状态切成可查表微块,或在一个机器字内并行处理多位,从而省去对数因子。为全部 的位型存类别、偏移、原块及局部前缀计数,表空间为 ,也计入单实例。两级目录加一次微表查询便给出常数时间access/rank,总空间为
表可按全部短位型构造,一个直接上界为 次字操作;读入并编码位串另付 。小规模和空串单独处理,渐近式不要求对 取对数。此界的低阶项相对于位串长度 ,不承诺在极稀疏时也是 。
查询实现的速度界不能混用
完整RRR的常数select还要加长短间隔索引。[1, §4.1] 旧静态select结构需要读取少量 位局部窗口;此处一个窗口跨常数个压缩块,经微表即可恢复,所以可以复用该索引,仍须计入它的 空间。这个组合与用rank二分是两种实现。
本页Python参考器选择可逐位核验的实现:文件仅存实际字节,装载时校验全目录、类内未用码和末字节零填充;查询时组合逆秩解一个块,select用rank二分。一次块解码检查 个二项式值;一次rank另读 位,二项式求值及Python大整数运算的成本另外支付。select再乘 次rank调用。这份程序没有构造常数查询微表,不能拿理论查表时间给它记账。
终点任务:重建上述123位文件,逐项核rank与select,再将1的位置交给分块Elias–Fano理路分块 Elias–Fano 与精确分区Partitioned Elias–Fano · Partitioned Elias-Fano · PEF按局部跨度选择连续段、位图或Elias–Fano负载,将目录费纳入分区递推,并以实际位流恢复访问和后继查询。。比较时分别列负载、导航目录、文件头与字节填充,见分块压缩练习。
参考资料
- Rajeev Raman、Venkatesh Raman、S. Srinivasa Rao,Succinct Indexable Dictionaries with Applications to Encoding k-ary Trees, Prefix Sums and Multisets,2007作者稿,§1.1.3、§4.1 Lemma4.1,PDF/印刷pp.12–13:完整位向量接口、类别/偏移和选择索引。本文用 表示位串长度,避免与原文的元素数记号混淆。
- Carl Kingsford,Indexable Compressed Bitvectors,CMU 02-714,PDF3–13页:分块、两个前缀目录及组合乘积计数;本文端点采用本库半开rank约定,并单独固定类别码宽和末块长度。