平衡树
前言:
关于二叉搜索树的内容,请参见大佬的博客(逃~)
而平衡树就是一种基于各种鬼畜操作使二叉搜索树保持平衡的数据结构
从而保证各种操作达到 $O(logn)$ 的均摊复杂度
下文主要介绍 $splay$,$treap$ 和替罪羊 3 种平衡树
正文:
splay
关于 $splay$,貌似并没有什么东西来保证它的平衡
维护平衡的核心操作好像只有不断的把目标节点旋转到根上
但它就是平衡的(哎?)
关于复杂度的证明,好像需要什么势能分析
是不是找物理老师来证比较好(皮~)
有了 $splay$ 之后,我们就可以种连猫树($Link - Cat - Tree$ ) 了 (开心)
同时 $splay$ 还可以维护区间,实现区间翻转等操作
由于 $splay$ 的码量较大,而本人较懒,所以只在维护 $LCT$ 时会写 $splay$
这里就给出用 $splay$ 维护 $LCT$ 时的代码
void rotate(int now) { int fa=tree[now].fa,anc=tree[fa].fa; int dir1=now==tree[fa].son[1]; if(!is_splay_root(fa)) { int dir2=fa==tree[anc].son[1]; tree[anc].son[dir2]=now; } tree[now].fa=anc; tree[fa].son[dir1]=tree[now].son[!dir1]; if(tree[fa].son[dir1]) tree[tree[fa].son[dir1]].fa=fa; tree[fa].fa=now; tree[now].son[!dir1]=fa; update(fa),update(now); } void splay(int now) { if(!now) return; //以下部分为维护LCT时的操作 int tmp=now; top=0; while(!is_splay_root(tmp)) stack[++top]=tmp,tmp=tree[tmp].fa; pushdown(tmp); while(top) pushdown(stack[top--]); //以上部分为维护LCT时的操作 while(!is_splay_root(now)) { if(!is_splay_root(tree[now].fa)) { int fa=tree[now].fa,anc=tree[fa].fa; if((now==tree[fa].son[1])^(fa==tree[anc].son[1])) rotate(now); else rotate(fa); } rotate(now); } }
treap
$treap = tree + heap$
也就是说 $treap$ 既满足二叉搜索树性质又满足堆性质
我们通过给每一个节点赋一个随机键值来保证它的复杂度
而通过旋转来维护堆性质的带旋$treap$ ,它死了
所以我们常写的 $treap$ 都是 $fhq\ treap$,也就是非旋 $treap$
关于非旋 $treap$,它也可以维护一个区间
此外,它可以可持久化(然而我并没有写过)
封装好的 $fhq\ treap$ 代码如下(过了大佬的 hack 数据,开心)
#include<cstdio> const int maxn=111111; #define inf (0x7fffffff) int get_rand() { static int seed=19260817; return seed=(((seed^142857)+200303)*2019)%0x3f3f3f3f; } struct node { int son[2]; int w,key,size; node(){} node(int w):w(w) { size=1; key=get_rand(); son[0]=0,son[1]=0; } }; int tree_cnt; node tree[maxn]; class fhq_treap { private: int root; int newnode(int w) { tree[++tree_cnt]=node(w); return tree_cnt; } void update(int now) { tree[now].size=tree[tree[now].son[0]].size+tree[tree[now].son[1]].size+1; } void split(int now,int num,int &x,int &y) { if(!now) { x=0,y=0; return; } if(tree[now].w<=num) { x=now; split(tree[now].son[1],num,tree[now].son[1],y); } else { y=now; split(tree[now].son[0],num,x,tree[now].son[0]); } update(now); } int merge(int x,int y) { if(!x||!y) return x+y; if(tree[x].key<tree[y].key) { tree[x].son[1]=merge(tree[x].son[1],y); update(x); return x; } else { tree[y].son[0]=merge(x,tree[y].son[0]); update(y); return y; } } public: fhq_treap():root(0){} void insert(int num) { int x,y; split(root,num,x,y); root=merge(merge(x,newnode(num)),y); } void remove(int num) { int x,y,z; split(root,num,x,z); split(x,num-1,x,y); y=merge(tree[y].son[0],tree[y].son[1]); root=merge(merge(x,y),z); } int get_rank(int num) { int x,y; split(root,num-1,x,y); int rank=tree[x].size+1; root=merge(x,y); return rank; } int get_kth(int k) { int now=root,flag=0; while(now) { int left_size=tree[tree[now].son[0]].size; if(k<=left_size) now=tree[now].son[0],flag=0; else if(k>left_size+1) k-=left_size+1,now=tree[now].son[1],flag=1; else return tree[now].w; } return flag?inf:-inf; } int get_pre(int num) { int rank=get_rank(num); return get_kth(rank-1); } int get_suf(int num) { int rank=get_rank(num+1); return get_kth(rank); } }treap; int main() { int n; scanf("%d",&n); while(n--) { int opt,x; scanf("%d%d",&opt,&x); switch(opt) { case 1: treap.insert(x); break; case 2: treap.remove(x); break; case 3: printf("%d\n",treap.get_rank(x)); break; case 4: printf("%d\n",treap.get_kth(x)); break; case 5: printf("%d\n",treap.get_pre(x)); break; case 6: printf("%d\n",treap.get_suf(x)); break; } } return 0; }
替罪羊树
替罪羊树应该维护平衡的方法应该是最暴力的一个
它定义了一个 const double alpha=0.75(一般取0.7~0.8之间的数值,都可以保证树的平衡)
如果左子树节点数/右子树节点数 > 整个子树节点数 * $alpha$,则把这颗子树 pia 扁重构
替罪羊树的优点是常数小,而且代码非常好写(虽然我是很晚才学会的)
另外它可以实现很多奇怪的操作,比如 $K-D tree$ 等
代码如下,跑的确实快啊
#include<cstdio> const int maxn=111111; #define inf (0x3f3f3f3f) const double alpha=0.75; struct node { int son[2]; int w,cnt,size; int size_point; node(){} node(int w):w(w) { cnt=1; size=1; size_point=1; son[0]=0,son[1]=0; } }; int tree_cnt; node tree[maxn]; #define left(x) (tree[x].son[0]) #define right(x) (tree[x].son[1]) class ScapeGoat { private: int root; int cnt,is_left[maxn]; int tail,trashcan[maxn]; int newnode(int num) { int id=(tail?trashcan[tail--]:++tree_cnt); tree[id]=node(num); return id; } void update(int now) { tree[now].size=tree[left(now)].size+tree[right(now)].size+tree[now].cnt; tree[now].size_point=tree[left(now)].size_point+tree[right(now)].size_point+1; } bool is_bad(int now) { int left_size=tree[left(now)].size_point; int right_size=tree[right(now)].size_point; return (left_size>tree[now].size_point*alpha)||(right_size>tree[now].size_point*alpha); } void pia(int now) { if(!now) return; pia(left(now)); if(tree[now].cnt) is_left[++cnt]=now; else trashcan[++tail]=now; pia(right(now)); } void build(int &now,int l,int r) { if(l>r) { now=0; return; } int mid=(l+r)>>1; now=is_left[mid]; build(left(now),l,mid-1); build(right(now),mid+1,r); update(now); } void rebuild(int &now) { cnt=0; pia(now); build(now,1,cnt); } void insert(int &now,int num) { if(!now) { now=newnode(num); return; } if(num==tree[now].w) tree[now].cnt++; else if(num<tree[now].w) insert(left(now),num); else insert(right(now),num); update(now); if(is_bad(now)) rebuild(now); } void remove(int now,int num) { if(!now) return; if(num==tree[now].w) {if(tree[now].cnt) tree[now].cnt--;} else if(num<tree[now].w) remove(left(now),num); else remove(right(now),num); update(now); } int get_rank(int now,int num) { int ans=1; while(now) { if(num==tree[now].w) {ans+=tree[left(now)].size;break;} else if(num<tree[now].w) now=left(now); else ans+=tree[left(now)].size+tree[now].cnt,now=right(now); } return ans; } int get_kth(int now,int k) { int flag=0; while(now) { if(k<=tree[left(now)].size) now=left(now),flag=0; else { k-=tree[left(now)].size; if(k<=tree[now].cnt) return tree[now].w; else k-=tree[now].cnt,now=right(now),flag=1; } } return flag?inf:-inf; } public: void insert(int num) { insert(root,num); } void remove(int num) { remove(root,num); } int get_rank(int num) { return get_rank(root,num); } int get_kth(int k) { return get_kth(root,k); } int get_pre(int num) { int rank=get_rank(num); return get_kth(rank-1); } int get_suf(int num) { int rank=get_rank(num+1); return get_kth(rank); } }sg; int main() { int n; scanf("%d",&n); while(n--) { int opt,x; scanf("%d%d",&opt,&x); switch(opt) { case 1: sg.insert(x); break; case 2: sg.remove(x); break; case 3: printf("%d\n",sg.get_rank(x)); break; case 4: printf("%d\n",sg.get_kth(x)); break; case 5: printf("%d\n",sg.get_pre(x)); break; case 6: printf("%d\n",sg.get_suf(x)); break; } } return 0; }
后序:
除了以上提到的三种平衡树外
还有 $AVL$ 树,$SB\ tree$,以及码量巨大的红黑树
本蒟太蒻了,所以都不会写,有感兴趣的童鞋可以自己百度一下

浙公网安备 33010602011771号