平衡树是什么?
其实平衡树就是支持旋转的二叉查找树。
什么是二叉查找树呢?
其实就是满足(左子树(全部节点) < 根 < 右子树(全部节点))的一棵树,比如↓
(注意并不是每个节点都要是满的,也就是说它不一定是一棵完全二叉树)
那么二叉查找树有什么用呢?
假如我们要查询第k大的数,我们从根节点开始往下走,假如左子树大小大于等于k,我们就进入左子树去找;如果左子树大小等于(k-1),就输出当前节点;假如左子树大小小于(k-1),我们就进入右子树找排名为(k-(左子树大小+1))的数。
这样由于树的理想高度为logn,所以单次查找复杂度为O(logn).
同理,插入、删除、查询前驱后继等操作的复杂度都为O(logn).
可是,二叉查找树并不能处理特殊情况,比如一个递增数列,那么如果你一个一个插入的话二叉查找树就退化成了一条链,不具备任何优越性了。
所以有前辈发明了Treap.
Treap是一个同时具有二叉查找树与堆的性质的树形结构(堆的本质也是二叉树)。比如↓
具体的实现过程就是我们在新加入一个节点时赋给他本身键值的同时给他一个优先级,而这个优先级随机产生,那就不怕被特殊数据卡了对不对!
那我们怎么维护堆的性质呢?
——旋转。
假如左图中x的优先级大于u,那就要通过右旋把x旋转到根的位置,同时为了维护二叉查找树的性质,所以要把x的右儿子接到u上。
(自己举个例子画画图就明白了)。
旋转代码:
inline void rotate(Node* &cur,int d) { Node *k = cur->ro[d^1] ; cur->ro[d^1] = k->ro[d] ; k->ro[d] = cur ; cur->pushup() ; k->pushup() ; cur = k ; }
(ro[0],ro[1]分别代表左右儿子,当d等于0时表示左旋,等于1时表示右旋)
给出模板题与参考代码:(我建的是大根堆)
https://www.luogu.org/problemnew/show/P3369
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int INF = (1<<29) ; inline int read() { int k = 0 , f = 1 ; char c = getchar() ; for( ; !isdigit(c) ; c = getchar()) if(c == '-') f = -1 ; for( ; isdigit(c) ; c = getchar()) k = k*10 + c-'0' ; return k*f ; } inline int randomm() { static int seed = 707 ; return seed = seed*3214567%23456789 ; } struct Node { int key, siz, rap ; Node *ro[2] ; Node(int x) { siz = 1 ; key = x ; rap = randomm() ; ro[1] = ro[0] = NULL ; } inline int pushup() { siz = 1 ; if(ro[0]) siz += ro[0]->siz ; if(ro[1]) siz += ro[1]->siz ; } inline int cop(int x) { if(x == key) return -1 ; return x < key ? 0 : 1 ; } }; int n, x, k, accm, ans ; inline void rotate(Node* &cur,int d) { Node *k = cur->ro[d^1] ; cur->ro[d^1] = k->ro[d] ; k->ro[d] = cur ; cur->pushup() ; k->pushup() ; cur = k ; } void insert(Node* &cur) { if(!cur) { cur = new Node(x) ; return ; } int d = x < cur->key ? 0 : 1 ; insert(cur->ro[d]) ; if(cur->ro[d]->rap > cur->rap) rotate(cur,d^1) ; else cur->pushup() ; } void Delete(Node* &cur) { int d = cur->cop(x) ; if(d == -1) { if(!cur->ro[0]) cur = cur->ro[1] ; else if(!cur->ro[1]) cur = cur->ro[0] ; else { int dd = cur->ro[0]->rap < cur->ro[1]->rap ? 0 : 1 ; rotate(cur,dd) ; Delete(cur->ro[dd]) ; } } else Delete(cur->ro[d]) ; if(cur) cur->pushup() ; } void rank(Node *cur) { if(!cur) return ; if(x < cur->key) { rank(cur->ro[0]) ; return ; } int lsiz = cur->ro[0] == NULL ? 0 : cur->ro[0]->siz ; if(x == cur->key) { ans = min(ans,accm+lsiz+1) ; rank(cur->ro[0]) ; } else { accm += (lsiz+1) ; rank(cur->ro[1]) ; } } void kth(Node *cur) { int cm = 1 ; if(cur->ro[0]) cm += cur->ro[0]->siz ; if(cm == k) { ans = cur->key ; } else if(k < cm) { kth(cur->ro[0]) ; } else { k -= cm ; kth(cur->ro[1]) ; } } void pre(Node *cur) { if(!cur) return ; if(cur->key < x) { ans = max(ans,cur->key) ; pre(cur->ro[1]) ; } else pre(cur->ro[0]) ; } void sub(Node *cur) { if(!cur) return ; if(cur->key > x) { ans = min(ans,cur->key) ; sub(cur->ro[0]) ; } else sub(cur->ro[1]) ; } int main() { n = read() ; Node *root = NULL ; int opt ; while(n--) { opt = read() ; switch(opt) { case 1:x = read(), insert(root) ; break ; case 2:x = read(), Delete(root) ; break ; case 3:x = read(), accm = 0, ans = INF, rank(root), printf("%d ",ans) ; break ; case 4:k = read(), kth(root), printf("%d ",ans) ; break ; case 5:x = read(), ans = -INF, pre(root), printf("%d ",ans) ; break ; case 6:x = read(), ans = INF, sub(root), printf("%d ",ans) ; break ; } } return 0 ; }
再给出一道基础应用题:
https://www.luogu.org/problemnew/show/P1503
(未完待续)