Treap是一种平衡二叉树,不过Treap会记录一个优先级(一般来说是随机生成),即Treap在以关键码构成二叉搜索树的同时,还会按照优先级的高低满足堆的性质,因此得名Treap(Tree + Heap)。 Treap不是二叉堆,二叉堆必须是完全二叉树,但Treap不必是。
对于每个结点,该结点的优先级不大于其所有孩子的优先级。Treap引入优先级的原因就是防止BST(二叉搜索树)退化成一条链,从而影响查询效率。
所以对于结点上的关键字来说,它是一颗BST,而对于结点上的优先级来讲,它是一个最小堆。其平均查找长度为\(~O(\log n)~\)。 Treap有插入、删除、旋转和查询等基本操作,进而可以实现查询第\(~k~\)大和查询关键字\(~x~\)排名等功能。
Treap的结构
Treap是一颗BST,所以Treap的每一个结点都需要记录一个关键字和两个儿子指针; Treap又是一个小顶堆,所以需要记录一个优先级。 结点的构建方式如下:
1 | struct Treap_Node |
Treap的操作
旋转
Treap本身对于关键字的构建和二叉查找树相同,但为对优先级维持其最小堆的性质,需要对树的结构进行调整,称为旋转,其操作方式如下:
- 左旋:将子树的根结点旋转到其根的左子树位置,同时根节点的右子节点成为该子树的根;
- 右旋:将子树的根结点旋转到其根的右子树位置,同时根节点的左子节点成为该子树的根。
例如下面这两棵树(表示方法为[关键字-优先级]
)可以互相对根结点旋转得到:
1 | [4-3] [2-1] |
旋转的实例代码如下:
1 | void Left_Rotate(Treap_Node *&a) //左旋 节点指针一定要传递引用 |
可以用下标来压缩代码量,具体实现方法在后边的模板给出。
插入
在Treap中插入元素的法则与在BST中插入的法则相同,但插入完成后可能会破坏堆的性质,所以插入完成后要进行旋转,具体方法如下:
- 从根结点开始访问;
- 若当前结点为空,则直接插入,否则执行下一步;
- 递归访问左右子树:
- 若插入的关键字小于当前访问结点,则访问其左子树,若插入后左子结点的优先级小于当前访问结点的优先级,则对当前结点进行右旋;
- 若插入的关键字大于当前访问结点,则访问其右子树,若插入后右子结点的优先级小于当前访问结点的优先级,则对当前结点进行左旋;
以下是一个例子:
先在Treap中按照BST的方法插入[3-2]
结点:
1 | [2-1] |
由于当前访问结点[4-5]
的左子结点[3-2]
的优先级小于当前结点的优先级,需要进行右旋操作:
1 | [2-1] [2-1] |
至此[3-2]
和[4-5]
已经调整完毕,向上回溯发现[5-4]
的左子结点[3-2]
优先级低于[5-4]
,故对[5-4]
进行一次右旋操作:
1 | [2-1] [2-1] |
至此,整个树调整完毕,插入操作结束。
插入的示例代码如下:
1 | Treap_Node *root; |
查询
与BST相同的二分查找,查询复杂度为\(~O(\log n)~\)。
删除
Treap的删除是基于旋转操作的,很容易理解的便是,只需要将要删除的结点旋转为叶子结点,再执行删除,具体步骤如下:
- 先在Treap中找到该结点,则有两种情况分述如下;
- 该节点为叶节点或链节点;
- 该节点有两个非空子节点;
- 针对情况1,若该节点有非空子结点,则用非空子节点代替该结点,否则用空节点代替该结点,然后删除该结点;
- 针对情况2,要先通过旋转使该结点使之可以直接删除,针对旋转有两种情况,分述如下:
- 如果该结点的左子结点的优先级比右子结点低,需要右旋该结点,使该结点降为右子树的根结点,然后跳转到右子树的根结点,重新判断;
- 反之,则左旋该结点,使该结点降为左子树的根结点,然后访问左子树的根,不断操作下去,直到该结点可以直接删除。
以上操作复杂度为\(~O(\log N)~\)。
示例代码如下:
1 | Treap_Node *root; |
拆分
要把一个Treap按大小分成两个Treap,只要在需要分开的位置加一个虚拟结点,然后旋至根结点,再删除,左右两个子树就是得出的两个Treap了。根据BST的性质,这时左子树的所有节点都小于右子树的节点。 拆分操作的复杂度与插入相同,也是\(~O(\log N)~\)。
合并
合并是指把两棵平衡树合并成一棵平衡树,其中第一棵树的所有结点都必须小于或等于第二棵树中的所有结点,这也是上面的拆分操作的结果所满足的条件,合并和拆分是互逆的。 Treap的合并操作的过程和分离完全相反,只要加一个虚拟的根,把两棵树分别作为左右子树,然后把根删除就可以了。 合并操作的复杂度与删除相同,也是\(~O(\log N)~\)。
Treap模板
Treap的功能一般比较固定,本文提供两种模板,分别来自kuagnbin和ACdreamer:
ACdreamer模板 (POJ 1442)
1 | #include <iostream> |
kuangbin模板 (ZOJ 3765)
1 | long long gcd(long long a, long long b) |
练习题目和参考资料
练习题目
- POJ 1442 Black Box
- SPOJ 3273 Order statistic set
- POJ 2761 Feed the dogs
- Hohocoder 1325 平衡树·Treap
- POJ 2985 The k-th LargestGroup
- HDU 4585 ShaoLin
- hdu 5096 ACM Rank