關於平衡樹(Treap)
平衡樹是什麽?
其實平衡樹就是支持旋轉的二叉查找樹。
什麽是二叉查找樹呢?
其實就是滿足(左子樹(全部節點) < 根 < 右子樹(全部節點))的一棵樹,比如↓
(註意並不是每個節點都要是滿的,也就是說它不一定是一棵完全二叉樹)
那麽二叉查找樹有什麽用呢?
假如我們要查詢第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\n",ans) ; break ; case 4:k = read(), kth(root), printf("%d\n",ans) ; break ; case 5:x = read(), ans = -INF, pre(root), printf("%d\n",ans) ; break ; case 6:x = read(), ans = INF, sub(root), printf("%d\n",ans) ; break ; } } return 0 ; }
再給出一道基礎應用題:
https://www.luogu.org/problemnew/show/P1503
(未完待續)
關於平衡樹(Treap)