平衡樹入門——Treap
挖坑一直爽,一直挖坑一直爽。這個坑大概鴿了一年。
1 平衡樹
平衡樹(Balance Tree,BT) 指的是,任意節點的子樹的高度差都小於等於1。常見的符合平衡樹的有,B樹(多路平衡搜索樹)、AVL樹(二叉平衡搜索樹)等。平衡樹可以完成集合的一系列操作, 時間複雜度和空間複雜度相對於“2-3樹”要低,在完成集合的一系列操作中始終保持平衡,爲大型數據庫的組織、索引提供了一條新的途徑。
\(Treap\) 是每個 OIer 入門必學平衡樹。
2 變量簡介
2.1 節點
struct node{
int size,cnt;
int val,rad;
int l,r;
};
node tree[N];
int tail,root;
其中 \(size\) 是這顆子樹的大小,\(cnt\) 表示的是這個節點所代表權值的個數,\(val\) 表示的是節點代表權值,\(rad\) 是一個隨機數,隨機是因爲 Treap 爲了保持其平衡,同時維護堆的性質,這樣複雜度就有了保障。
2.2 合併信息
inline void pushup(int k){
tree[k].size=tree[tree[k].l].size+tree[tree[k].r].size+tree[k].cnt;
}
比較顯然,不做講解。
2.3 左右旋
inline void zip(int &k){
int kr=tree[k].r;
tree[k].r=tree[kr].l;tree[kr].l=k;
pushup(k);pushup(kr);k=kr;
}
inline void zap(int &k){
int kl=tree[k].l;
tree[k].l=tree[kl].r;tree[kl].r=k;
pushup(k);pushup(kl);k=kl;
}
第一個函數是左旋,第二個是右旋。爲了維護這是爲了維護堆的性質,同時必須滿足 BST 的性質。
通過圖片不難理解上面的代碼。
2.4 建立新節點
inline int New(int val){
tree[++tail].val=val;
tree[tail].size=tree[tail].cnt=1;
tree[tail].rad=rand();
return tail;
}
2.5 插入與刪除
inline void insert_(int &k,int val){
if(tail==0) k=0;
if(!k) k=New(val);
else if(val==tree[k].val) tree[k].cnt++;
else if(val<tree[k].val){
insert_(tree[k].l,val);
if(tree[tree[k].l].rad>tree[k].rad) zap(k);
}
else{
insert_(tree[k].r,val);
if(tree[tree[k].r].rad>tree[k].rad) zip(k);
}
pushup(k);
}
插入實際上就是在爲這個權值找一個新的位置,與此同時維護一下堆的性質,最後合併就可以了。
inline void delete_(int &k,int val){
if(!k) return;
if(val==tree[k].val){
if(tree[k].cnt>1){
tree[k].cnt--;
}
else if(!tree[k].l&&!tree[k].r) k=0;
else{
if(!tree[k].r||tree[tree[k].l].rad>tree[tree[k].r].rad){
zap(k);
delete_(tree[k].r,val);
}
else{
zip(k);
delete_(tree[k].l,val);
}
}
}
else val<tree[k].val?delete_(tree[k].l,val):delete_(tree[k].r,val);
pushup(k);
}
我們希望把刪除節點旋轉到葉子節點上進行刪除,當然進行這個操作得必須是這個點的 \(cnt\) 在刪除之前爲 \(1\)
。注意在旋轉的過程中,還是要小心維護其堆得性質,同時因爲我們的引用符號,我們直接在旋轉到葉子節點之後讓 \(k=0\) 就完成了刪除,非常方便。
2.6 查詢前驅後繼,按排名找值,按值找排名
inline int getrank(int k,int val){
if(!k) return -INF;
if(tree[k].val==val) return tree[tree[k].l].size+1;
if(val<tree[k].val) return getrank(tree[k].l,val);
return getrank(tree[k].r,val)+tree[tree[k].l].size+tree[k].cnt;
}
inline int getval(int k,int rank){
if(!k) return -INF;
if(tree[tree[k].l].size>=rank) return getval(tree[k].l,rank);
if(tree[tree[k].l].size+tree[k].cnt>=rank) return tree[k].val;
return getval(tree[k].r,rank-tree[tree[k].l].size-tree[k].cnt);
}
inline int getpre(int k,int val){
if(!k) return -INF;
if(tree[k].val>=val) return getpre(tree[k].l,val);
else return Max(tree[k].val,getpre(tree[k].r,val));
}
inline int getnext(int k,int val){
if(!k) return INF;
if(tree[k].val<=val) return getnext(tree[k].r,val);
else return Min(tree[k].val,getnext(tree[k].l,val));
}
這個好理解,不做講解。