关于平衡树的一些总结
平衡树是个大专题啊qwq。。最近也学了一些很有用的平衡树,写个总结吧。。
一.splay
学的第一个平衡树,复习一下。。
splay是一个功能很强大的二叉搜索树。其实讲道理splay并不算平衡树吧,因为它并没有任何关于树高的限制。splay的原理就是,每次插入或查询一个结点,就把它旋转到根结点,这样与搜索引擎的原理类似,查询次数较多的结点就能更靠近根。splay并不能保证每次操作严格复杂度都是O(logn),但是每次操作的均摊复杂度确实可以是O(log)。。然而我也不知道为什么。。不过splay好像有可能会被卡来着。。但是splay在序列上的操作可以说几乎完美,因为它可以任意旋转。splay的编程复杂度也是很小的。
splay的核心在于splay操作,即调用splay(x,f),就是把x旋转到f的儿子上(特别地,如果把x旋到根那么f=0)。splay的旋转分为3种——zig,zig-zig,zig-zag。zig操作,就是当x的父亲已经是f的儿子时,直接旋转x。zig-zig,当x与x的父亲位于同一侧时(就是说,x是x的父亲的左儿子,x的父亲是x的父亲的父亲的左儿子,反之也成立)。先旋转x的父亲,再旋转x。zig-zag,当x与x的父亲位于不同侧时,直接旋转两次x。这里的旋转,就是指,如果x是它父亲的左儿子,那么就把他右旋,否则左旋。感觉上个图会更容易理解些。。
然后就是上代码了。。
1 il void rotate(RG int x){ 2 RG int y=fa[x],z=fa[y],k=ch[y][0]==x; 3 ch[z][ch[z][1]==y]=x,fa[x]=z; 4 ch[y][k^1]=ch[x][k],fa[ch[x][k]]=y; 5 ch[x][k]=y,fa[y]=x,pushup(y),pushup(x); return; 6 } 7 8 il void splay(RG int x,RG int goal){ 9 RG int top=0; st[++top]=x; 10 for (RG int i=x;fa[i]!=goal;i=fa[i]) st[++top]=fa[i]; 11 for (RG int i=top;i;--i) if (lazy[st[i]]) pushdown(st[i]); 12 while (fa[x]!=goal){ 13 RG int y=fa[x],z=fa[y]; 14 if (fa[y]!=goal){ 15 ((ch[z][0]==y)^(ch[y][0]==x)) ? rotate(x) : rotate(y); 16 } 17 rotate(x); 18 } 19 if (!goal) rt=x; return; 20 }
splay还是很好写的吧,还能对一个序列进行分裂和合并这种神奇的操作qwq。。
二.替罪羊树
替罪羊树也是一种很好写的平衡树qwq。。替罪羊树的核心思想就是重构。即当一棵子树的平衡被破坏,那么就把这棵树拍平,也就是树高为O(logn)的完美二叉树形态。这样看似复杂度很高,实则不然。可以证明,替罪羊树每次重构的复杂度都是均摊O(logn)的。反正我也不会证明,证明还要用物理学知识。。
---------------------------------------------------------------------------------------------------------------------------------------------------
具体是这样操作的:我们设定一个参数α,满足α∈[0.5,1)。(其实如果你设定α为0.5那就会T飞~)
α高度平衡:树高h≤log(1/a)n
α大小平衡:对于一个结点p满足max(sizelp,sizerp)≤αsizep
如果每个结点均满足α大小平衡,假设最深的点为dd,那么有1≤nα^depd
那么depd≤logα(1/n)=log(1/a)n,即满足α高度平衡。
插入和普通BST类似,只需要判断插入操作是否导致了这一条链上结点的大小平衡被破坏,如果有的话将深度最浅的点所在子树暴力重建。重建最简单的方法就是对这棵子树中序遍历后分治建树。通过势能分析可以证明每一次插入的均摊复杂度为logn。
查询和普通BST并无区别。
删除操作比较巧妙。对于一次删除,我们并不马上移除这个点,而是直接这个点上打上删除标记,查询时跳过该点。重构时可以顺便删除打了删除标记的点。当被删除结点的个数超过总结点数的(1−α)倍时可以选择重构整棵树进行结构优化,并且显然这部分重构的复杂度不会超过O(nlogn)。
---------------------------------------------------------------------------------------------------------------------------------------------------
——以上内容蒯自xLightGod学长(http://blog.xlightgod.com/%E3%80%90bzoj3224%E3%80%91%E6%99%AE%E9%80%9A%E5%B9%B3%E8%A1%A1%E6%A0%91/)
所以呢,上代码吧。。http://www.cnblogs.com/wfj2048/p/6498757.html(bzoj3224 普通平衡树)
1 //It is made by wfj_2048~ 2 #include <algorithm> 3 #include <iostream> 4 #include <complex> 5 #include <cstring> 6 #include <cstdlib> 7 #include <cstdio> 8 #include <vector> 9 #include <cmath> 10 #include <queue> 11 #include <stack> 12 #include <map> 13 #include <set> 14 #define N (100010) 15 #define il inline 16 #define RG register 17 #define ll long long 18 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) 19 20 using namespace std; 21 22 int ch[N][2],cover[N],sz[N],val[N],del[N],st[N],Q,rt,top,tot; 23 const double alpha=0.75; 24 25 il int gi(){ 26 RG int x=0,q=1; RG char ch=getchar(); while ((ch<'0' || ch>'9') && ch!='-') ch=getchar(); 27 if (ch=='-') q=-1,ch=getchar(); while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; 28 } 29 30 il void pushup(RG int x){ 31 sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+(!del[x]); 32 cover[x]=cover[ch[x][0]]+cover[ch[x][1]]+1; return; 33 } 34 35 il void dfs(RG int x){ 36 if (ch[x][0]) dfs(ch[x][0]); 37 if (!del[x]) st[++top]=x; 38 if (ch[x][1]) dfs(ch[x][1]); 39 sz[x]=cover[x]=ch[x][0]=ch[x][1]=0; 40 return; 41 } 42 43 il int divide(RG int l,RG int r){ 44 if (l>r) return 0; RG int mid=(l+r)>>1; 45 ch[st[mid]][0]=divide(l,mid-1); 46 ch[st[mid]][1]=divide(mid+1,r); 47 pushup(st[mid]); return st[mid]; 48 } 49 50 il void rebuild(RG int &x){ top=0,dfs(x),x=divide(1,top); return; } 51 52 il int qrank(RG int x,RG int k){ 53 RG int res=1; 54 while (x){ 55 if (k<=val[x]) x=ch[x][0]; 56 else res+=sz[ch[x][0]]+(!del[x]),x=ch[x][1]; 57 } 58 return res; 59 } 60 61 il int find(RG int x,RG int k){ 62 while (x){ 63 if (!del[x] && k==sz[ch[x][0]]+1) return val[x]; 64 if (k<=sz[ch[x][0]]) x=ch[x][0]; 65 else k-=sz[ch[x][0]]+(!del[x]),x=ch[x][1]; 66 } 67 return val[x]; 68 } 69 70 il int* Insert(RG int &x,RG int k){ 71 if (!x){ val[x=++tot]=k,sz[x]=cover[x]=1; return NULL; } 72 sz[x]++,cover[x]++; int *p=Insert(ch[x][val[x]<=k],k); 73 if (max(cover[ch[x][0]],cover[ch[x][1]])>cover[x]*alpha) p=&x; 74 return p; 75 } 76 77 il void insert(RG int k){ int *x=Insert(rt,k); if (x) rebuild(*x); return; } 78 79 il void Erase(RG int x,RG int k){ 80 while (x){ 81 sz[x]--; if (!del[x] && k==sz[ch[x][0]]+1){ del[x]=1; return; } 82 if (k<=sz[ch[x][0]]) x=ch[x][0]; else k-=sz[ch[x][0]]+(!del[x]),x=ch[x][1]; 83 } 84 return; 85 } 86 87 il void erase(RG int k){ Erase(rt,qrank(rt,k)); if (sz[rt]<cover[rt]*alpha) rebuild(rt); return; } 88 89 il void work(){ 90 Q=gi(); RG int type,x; 91 while (Q--){ 92 type=gi(),x=gi(); 93 if (type==1) insert(x); 94 if (type==2) erase(x); 95 if (type==3) printf("%d\n",qrank(rt,x)); 96 if (type==4) printf("%d\n",find(rt,x)); 97 if (type==5) printf("%d\n",find(rt,qrank(rt,x)-1)); 98 if (type==6) printf("%d\n",find(rt,qrank(rt,x+1))); 99 } 100 return; 101 } 102 103 int main(){ 104 File("tree"); 105 work(); 106 return 0; 107 }
三.SBT
一种很快的平衡树,而且平衡条件很奇葩。。
SBT的平衡要求是,对于一个结点,它的size必须大于等于它的兄弟的左右儿子的size。
如果不满足这个平衡性,我们就要对这个点进行maintain操作,也就是修复操作。
假设我们修复ch[x][1],ch[x][0]的情况不做讨论,因为是对称的。
1.size[ch[x][1]]<size[ch[ch[x][0]][0]],直接将x右旋。
2.size[ch[x][1]]<size[ch[ch[x][0]][1]],先将ch[x][0]左旋,然后再将x右旋。
最后,我们再重新修复x的儿子,以及x。
我们记一个k,k为1表示修复x的右儿子,k为0表示修复x的左儿子,k=0与上述情况类似。
下面上代码:(注意这里的旋转和splay的旋转不一样!!SBT的旋转是把当前点往下旋转~)
1 il void rotate(RG int &x,RG int k){ 2 RG int y=ch[x][k^1]; ch[x][k^1]=ch[y][k],ch[y][k]=x; 3 s[y]=s[x],s[x]=s[ch[x][0]]+s[ch[x][1]]+1,x=y; 4 return; 5 } 6 7 il void maintain(RG int &x,RG int k){ 8 if (s[ch[ch[x][k^1]][k^1]]>s[ch[x][k]]) rotate(x,k); 9 else if (s[ch[ch[x][k^1]][k]]>s[ch[x][k]]) rotate(ch[x][k^1],k^1),rotate(x,k); 10 else return; 11 maintain(ch[x][0],1),maintain(ch[x][1],0); 12 maintain(x,1),maintain(x,0); return; 13 }
可以说,SBT的代码量还是很短的,而且很方便。复杂度证明。。好像和斐波那契数列有关系来着。。SBT可以做到树高最多为O(logn),真的很神奇啊。。
我感觉很醉,连treap都没学,如果考可持久化怎么办。。