[Treap][學習筆記]
阿新 • • 發佈:2018-11-30
rip section 及其 min 當前 imageview urn 其他 cimage
平衡樹
平衡樹就是一種可以在log的時間復雜度內完成數據的插入,刪除,查找第k大,查詢排名,查詢前驅後繼以及其他許多操作的數據結構。
Treap
treap是一種比較好寫,常數比較小,可以實現平衡樹基本操作的一種平衡樹。treap的平衡是基於隨機化。是將堆與二叉查找樹結合起來所得到的數據結構。
treap在插入數時,給每個數賦了一個新的隨機的值id。在以後的操作中,必須始終使得treap中的id構成一個堆的形態才可以。這樣就達到了平衡的目的。
定義
struct node {
int ch[2],val,id,cnt,siz;
}TR[N];
ch[0/1]分別表示左右兒子。
val是插入的數
id是賦給這個數的一個隨機的值
cnt表示這個數字出現的次數
siz為在treap中以這個點為根的數字的個數
更新
void up(int cur) {
TR[cur].siz = TR[ls].siz + TR[rs].siz + TR[cur].cnt;
}
就是維護一下siz
旋轉
f表示將左(0)還是右(1)兒子旋轉上來。
如圖
加入我們現在要把2旋轉到6這個位置,那麽旋轉之後6肯定成為了2的右兒子,那4去哪裏呢。我們就把4變成6的左兒子,這樣就完成了旋轉。然後在維護一下siz就行了。
就成了這樣
因為treap不存父親,所以這裏要取地址,把6換成2
void rotate(int &cur,int f) { int son = TR[cur].ch[f]; TR[cur].ch[f] = TR[son].ch[f ^ 1]; TR[son].ch[f ^ 1] = cur; up(cur); cur = son; up(cur); }
插入
插入一個元素時我們只要按照與二叉查找樹相同的方法,找到一個合適的位置插入即可。如果這個元素以前已經插入過了。那麽只要把這個數的cnt++就行了。插入完成之後,還要維護id滿足堆的形態這個條件。所以如果插入之後的兒子的id比當前節點的id小了,那麽就將兒子旋轉上來就行了。
void insert(int &cur,int val) { if(!cur) { cur = ++tot; TR[cur].val = val; TR[cur].id = rand(); TR[cur].cnt = TR[cur].siz = 1; return; } TR[cur].siz++;//!! if(TR[cur].val == val) { TR[cur].cnt++;return;} int d = val > TR[cur].val; insert(TR[cur].ch[d],val); if(TR[TR[cur].ch[d]].id < TR[cur].id) rotate(cur,d); }
刪除
首先找到要刪除的節點,根據這個節點兒子的個數可以分為兩種情況。
情況1:這個節點有1個或者0個兒子。那麽直接將這個節點變為這個節點的兒子(沒有就是0)就行了
情況2:這個節點有2個兒子。那麽不斷的往下旋轉這個節點,知道滿足情況1。在旋轉的時候應該註意,因為要滿足堆這個條件。所以應該將兒子中id較小的那個旋轉上來。
void del(int &cur,int val) {
if(!cur) return;
if(val == TR[cur].val) {
if(TR[cur].cnt > 1) {TR[cur].cnt--;TR[cur].siz--; return;}
if(!ls || !rs) {cur = ls + rs;return;}
int d = TR[rs].id < TR[ls].id;
rotate(cur,d);
del(cur,val);
}
else TR[cur].siz--,del(TR[cur].ch[val > TR[cur].val],val);
}
查找排名
這個就真的和二叉搜索樹一樣了。如果要找的那個數字比當前節點大。那麽就將排名加上左子樹大小,然後搜右子樹,否則搜左子樹。
int Rank(int cur,int val) {
if(!cur) return 0;
if(val == TR[cur].val) return TR[ls].siz + 1;
if(val < TR[cur].val) return Rank(ls,val);
return Rank(rs,val) + TR[ls].siz + TR[cur].cnt;
}
查找第k大(排名為k的元素)
和查找排名差不多。只要不斷的記錄下要查找當前子樹中的第幾大。如果比左子樹大小加上根節點大小還大,那麽就減去左子樹大小和根大小,並查找右子樹,否則查找左子樹
int kth(int cur,int now) {
while(1) {
if(TR[ls].siz >= now) cur = ls;
else if(TR[ls].siz + TR[cur].cnt < now) now -=TR[ls].siz + TR[cur].cnt,cur = rs;
else return TR[cur].val;
}
}
前驅
還是從根往下搜索,如果要找的數比當前根節點要大,那麽就搜索右子樹,並將搜到的答案與當前根取max,否則就搜索左子樹
int pred(int cur,int val) {
if(!cur) return -INF;
if(val <= TR[cur].val) return pred(ls,val);//!!!
return max(pred(rs,val),TR[cur].val);
}
後繼
跟前驅同理
int nex(int cur,int val) {
if(!cur) return INF;
if(val >= TR[cur].val) return nex(rs,val);//!!!
return min(nex(ls,val),TR[cur].val);
}
一言
迷途經累劫,悟則剎那間。 ——六祖壇經
[Treap][學習筆記]