平衡樹是個大專題啊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都沒學,如果考可持久化怎么辦。。