平衡树
平衡树
欢迎来到二叉搜索树的世界,在这里,你将会见到:treap,红黑树,splay,sbt,AVL….
对于对二叉搜索树的操作,我们可以将其分成了:
- 有旋转(AVL,Splay,红黑树)
- 无旋转(替罪羊树,fhq Treap…)
下面进入本次的正题:平衡树Treap:
Treap
前置知识:
1.BST(Binary Search Tree)二叉搜索树
BST: 当前节点的左子树中的任何一个点的权值都是严格小于当前结点的权值,右子树反之
BST的中序遍历一定是一个有序序列
BST的本质:动态维护一个有序序列
2.Heap 堆(Treap使用的是大根堆的性质)
Heap,大根堆,即一颗所有根节点的权值大于所有其儿子节点的权值的数据结构.如图便是一个大根堆:
平衡树:
平衡树是一颗基于BST和Heap的数据结构
Treap解决的问题:
一颗随机的BST期望高度是logn,这样在进行插入和查询的时候的时间复杂度便是O(logn)级别。但是,如果随意的插入,二叉搜索树有可能退化成链表,其各种操作时间复杂度也就会退化成O(n)::
但是我们期望中的BST应该是:
因此,就诞生出了平衡树:对于每个点,我们不仅在权值上维护BST的性质,我们还维护一个val值,这个值是由随机生成的(越随机越好),然后我们通过一些奇奇怪怪的操作来一直维护这一棵二叉搜索树维持第二张图的性质,即之后会提到的左旋和右旋。
平衡树的用途:在只考虑前四种用途的情况下,我们可以借助STL中的set容器来代替平衡树(实际上set就是一颗红黑树)
1.插入:递归找到位置并插入 ___STL set insert()
2.删除:将删除的位置变成叶子节点之后删除 __STL erase
3.找前驱/后继:中序遍历中的前一个位置和后一个位置____STL_ set ++
1 | //找前驱伪代码 |
4.找最大值和最小值:STL_set___/begin()___/end()-1
5.求某一个值得排名
6.求排名是k的数是哪个
7.比某个数小的最大值
8.比某个数大的最小值
下面我们边看代码,边来解析Treap的实现原理
附加例题:https://www.luogu.com.cn/problem/P3369
Treap的存储
1 | Node{ |
Treap的维护值的更新
1 | void push_up(int &p){ //很类似与线段树,这个操作就是对于额外维护的信息,用子节点来更新父节点 |
Treap的创建节点
1 | void get_node(int key){ //创建一个权值为key的节点 |
Treap的左旋与右旋😇
这张图将会贯穿始终.
右旋操作即将根节点x的左儿子y旋转到根节点,然后y的右儿子“掉”到原来根节点x的左儿子上。我们可以在一系列操作在之后,加入y的val值大于x的val值了,那么为了维护val值满足大根堆的性质,我们可以对整棵树进行右旋操作
1 | void zig(int &p){ //对p节点进行右旋操作 |
左旋操作:左旋操作即从图中的右边变成左边:
1 | void zag(int &p){ |
一开始的建树:此时用了一个小小的trick,为了保证不出现边界问题,我们将原树先用负无穷和正无穷来建好框架,后面再往里面添加和删除即可:
1 | void build(){ |
插入操作
1 | //insert a value in position p |
删除操作,还是参考这张图
1 | void remove(int &p,int key){ |
对于下面的操作,只要明白Node中维护的cnt和size的含义就可以轻松写出来了
给定一个值来查找这个值在BST中序遍历中的排名:
1 | //get rank by given value |
给定一个中序遍历的排名,来求对应值:
1 | //get value by given rank |
求在Treap中,比给定数大的最小的数
1 | //get the greatest number less than key |
在Treap中,求比给定数小的最大的数
1 | //get the least number greater than key |
嗯。。。这里就结束了。这应该是目前为止遇到的第三难写的数据结构了…
附上例题的AC代码: (150+行,稍有不注意,眼角两行泪。一杯茶一包烟,百行代码调一天)
注意:因为一开始初始化了两个无穷节点,所以在查排名和获取排名的时候应该减去。
1 |
|
推荐一个超赞的数据结构可视化网站:https://visualgo.net/zh