无旋 Treap,又名 FHQ-Treap。无旋 Treap 仅有两种核心操作 —— 分裂与合并,它依靠这两种操作来维护树的平衡,从而省去了旋转操作。这种操作方式使得它天生支持维护序列、可持久化等特性。
本文提供使用原生指针和数组模拟指针两种方法实现的代码,可以点击代码块上方的切换按钮查看两种不同版本的代码。
#前言
Treap 是一种弱平衡的二叉搜索树。它的数据结构由二叉树和二叉堆组合形成,名字也因此为 tree 和 heap 的组合。
Treap 的每个结点上除了按照二叉搜索树排序的 值外要额外储存一个叫 的值。它由每个结点建立时随机生成,并按照最大堆性质排序。因此 Treap 除了要满足二叉搜索树的性质之外,还需满足父节点的 大于等于两个子节点的值。所以它是期望平衡的。搜索,插入和删除操作的期望时间复杂度为 。
#主要操作
#分裂
本节所介绍的分裂操作会按照排名将一棵树分裂为两棵子树,左子树包含 个元素,右子树包含剩余元素。
分割时,如果左子树中元素个数不足 个,就从右子树中分割 个元素放置到左子树中,反之则从左子树中划分出 个元素放置到右子树中。
不要忘记 pushup。
1 |
|
1 |
|
#按值分裂
本节介绍的分裂操作将会按照元素值的大小将一棵树分裂为两棵子树,左子树中的元素均不大于 ,右子树中的元素均大于 。
1 |
|
1 |
|
#合并
合并操作也就是将两棵 Treap 合并成一棵 Treap,其中一颗 Treap 的所有点的权值均小于或大于另一颗 Treap 任意点的权值。
1 |
|
1 |
|
#内部工具库
#获取子树大小
判断子树是否为空,若空返回 ,非空返回子树大小。
1 |
|
#查找元素 (find)
递归查找值为 的元素,根据 BST 的性质即可进行。
递归到某个节点时,先判断当前节点的元素值大小,若查询值小于当前节点的元素值时则向左子树递归,反之则向右子树递归。时间复杂度为 。
1 |
|
1 |
|
#封装函数
#插入
先将整个 Treap 按照给定的值分割成两个子树,再在这两个子树的中间插入新值即可。
1 |
|
1 |
|
#删除
本删除操作在遇到多个相同的数时只会删除一个。具体操作与插入类似,不再过多叙述。
1 |
|
1 |
|
#排名
将整棵树按值分裂,左子树的大小再加 即为该数的排名。
1 |
|
1 |
|
#第 k 大元素
将整棵树分裂为由前 个元素和其余节点组成的两棵子树,则右子树的第一个元素即为所求。
1 |
|
1 |
|
#代码
需要的外部函数:rand
(定义于头文件 <cstdlib>
)。
指针版和数组版的 Treap
类暴露出的接口是一样的。
1 |
|
1 |
|
#技巧
求数 的前驱
1 |
|
求数 的后继
1 |
|
#参考资料
- 谈谈各种数据结构(文件存档备份),范浩强,WC2012。
- Treap,OI Wiki,2022 年 2 月 7 日。
- 某科学的无旋 Treap(FHQ-Treap),星夜,2021 年 1 月 25 日。