模板:二叉搜索树平衡树
文章目錄
- 前言
- 二叉搜索樹(shù)
- 代碼
- treap
- 代碼
- splay
- 開(kāi)點(diǎn)
- 旋轉(zhuǎn)
- splay
- 插入
- 查找第k大元素
- 查找給定元素的排名
- 前驅(qū)&后繼
- 刪除
- 完整代碼
- 練習(xí)總結(jié)
前言
終于開(kāi)始學(xué)這個(gè)東西了
看了好幾篇博客才找到一篇可讀的qwq
我曾經(jīng)還以為線段樹(shù)碼量大…我真傻,真的
所謂平衡樹(shù),就是把二叉搜索樹(shù)加了一個(gè)隨機(jī)權(quán)值
并通過(guò)旋轉(zhuǎn)使這個(gè)權(quán)值始終符合堆的性質(zhì)
(treap=tree+heap)
我覺(jué)得平衡樹(shù)主要的功能就是維護(hù)排名相關(guān)的東西
(update:更正觀點(diǎn)!平衡樹(shù)最好用的地方還是區(qū)間問(wèn)題,排名問(wèn)題在序列上可以主席樹(shù),動(dòng)態(tài)的可以樹(shù)狀數(shù)組,為啥要寫(xiě)splay這種東西…)
前驅(qū)后繼這些其實(shí)都可以直接拿set偷懶
(當(dāng)然本剛學(xué)treap1h的蒟蒻的理解完全沒(méi)有參考價(jià)值)
一開(kāi)始WA成了60分qwq
千萬(wàn)注意一定不要落掉無(wú)處不在的pushup!
二叉搜索樹(shù)
不學(xué)BST,何以treap? ——魯迅
二叉搜索樹(shù)是一種二叉樹(shù)的樹(shù)形數(shù)據(jù)結(jié)構(gòu),其定義如下:
空樹(shù)是二叉搜索樹(shù)。
若二叉搜索樹(shù)的左子樹(shù)不為空,則其左子樹(shù)上所有點(diǎn)的附加權(quán)值均小于其根節(jié)點(diǎn)的值。
若二叉搜索樹(shù)的右子樹(shù)不為空,則其右子樹(shù)上所有點(diǎn)的附加權(quán)值均大于其根節(jié)點(diǎn)的值。
二叉搜索樹(shù)的左右子樹(shù)均為二叉搜索樹(shù)。
二叉搜索樹(shù)上的基本操作所花費(fèi)的時(shí)間與這棵樹(shù)的高度成正比。對(duì)于一個(gè)有 n個(gè)結(jié)點(diǎn)的二叉搜索樹(shù)中,這些操作的最優(yōu)時(shí)間復(fù)雜度為 Ologn,最壞為On。隨機(jī)構(gòu)造這樣一棵二叉搜索樹(shù)的期望高度為logn。
代碼
#include<bits/stdc++.h> using namespace std; const int N=1e5+100; #define ll long long int n,m,k; int x,y; int cnt[N],ls[N],rs[N],val[N],siz[N],tot=1; void insert(int &o,int v){//插入元素if(!o){o=++tot;val[o]=v;ls[o]=rs[o]=0;siz[o]=cnt[o]=1;}siz[o]++;if(val[o]==v) {cnt[o]++;return;}if(v<val[o]) insert(ls[o],v);else insert(rs[o],v); } int delmin(int &o){if(!ls[o]){int u=o;o=rs[o];return u;}else{int u=delmin(ls[o]);siz[o]-=cnt[u];return u;} } void del(int &o,int v){//刪除元素siz[o]--;if(val[o]==v){if(cnt[o]>1) cnt[o]--;else if(ls[o]&&rs[o]) o=delmin(rs[o]);else o=ls[o]+rs[o];return;}if(v<val[o]) del(ls[o],v);else del(rs[o],v); } int askrank(int o,int v){//查詢x的排名if(val[o]==v) return siz[ls[o]]+1;else if(val[o]>v) return askrank(ls[o],v);else return siz[ls[o]]+cnt[o]+askrank(rs[o],v); } int asknth(int o,int k){//查詢第k大的元素if(siz[ls[o]]>=k) return asknth(ls[o],k);else if(siz[ls[o]]+cnt[o]>=k) return val[o];else return asknth(rs[o],k-(siz[ls[o]]+cnt[o])); } int main(){scanf("%d",&n);int flag;val[1]=-2e9;int r=1;for(int i=1;i<=n;i++){scanf("%d%d",&flag,&x);if(flag==1) insert(r,x);else if(flag==2) del(r,x);else if(flag==3) printf("%d\n",askrank(1,x));else if(flag==4) printf("%d\n",asknth(1,x));}return 0; } /**/treap
旋轉(zhuǎn)是平衡樹(shù)的靈魂
一個(gè)很重要的技巧是利用0/1存儲(chǔ)左右兒子
這樣在旋轉(zhuǎn)的時(shí)候?qū)懫饋?lái)會(huì)容易很多
代碼
#include<bits/stdc++.h> using namespace std; const int N=1e6+2e5+100; #define ll long long int n,m,k; int x,y; int cnt[N],ch[N][2],val[N],siz[N],tot,r,dat[N]; int New(int v){val[++tot]=v;dat[tot]=rand();ch[tot][0]=ch[tot][1]=0;siz[tot]=cnt[tot]=1;return tot; } void pushup(int o){siz[o]=siz[ch[o][0]]+siz[ch[o][1]]+cnt[o]; } void build(){r=New(-2e9);ch[1][1]=New(2e9);pushup(r); } void rotate(int &o,int d){int temp=ch[o][!d];ch[o][!d]=ch[temp][d];ch[temp][d]=o;o=temp;pushup(o);pushup(ch[o][d]); } void insert(int &o,int v){if(!o){o=New(v);return;}if(v==val[o]){cnt[o]++;pushup(o);return;}int d= v>val[o];insert(ch[o][d],v);if(dat[ch[o][d]]>dat[o]) rotate(o,!d);pushup(o); } void del(int &o,int v){if(!o) return;if(v==val[o]){if(cnt[o]>1){cnt[o]--;pushup(o);return;}if(ch[o][0]||ch[o][1]){int d=!ch[o][1]||dat[ch[o][1]]<dat[ch[o][0]];rotate(o,d);del(ch[o][d],v);pushup(o);}else o=0;return;}if(v<val[o]) del(ch[o][0],v);else del(ch[o][1],v);pushup(o); } int getrank(int o,int v){if(!o) return 1;if(val[o]==v) return siz[ch[o][0]]+1;else if(v<val[o]) return getrank(ch[o][0],v);else return getrank(ch[o][1],v)+siz[ch[o][0]]+cnt[o]; } int getnth(int o,int k){if(!o) return 2e9;if(siz[ch[o][0]]>=k) return getnth(ch[o][0],k);else if(siz[ch[o][0]]+cnt[o]>=k) return val[o];else return getnth(ch[o][1],k-(siz[ch[o][0]]+cnt[o])); } int getpre(int v){int res=-2e9,p=r;while(p){if(val[p]<v){res=val[p];p=ch[p][1];}else p=ch[p][0];}return res; } int getnxt(int v){int res=2e9,p=r;while(p){if(val[p]>v){res=val[p];p=ch[p][0];}else p=ch[p][1];}return res; } int main(){scanf("%d%d",&n,&m);build();int flag;for(int i=1;i<=n;i++){scanf("%d",&x);insert(r,x);}int ans=0,lst=0;for(int i=1;i<=m;i++){scanf("%d%d",&flag,&x);x^=lst;if(flag==1) insert(r,x);else if(flag==2) del(r,x);else if(flag==3){int res=getrank(r,x)-1;lst=res;ans^=res;}else if(flag==4){int res=getnth(r,x+1);lst=res;ans^=res;}else if(flag==5){int res=getpre(x);lst=res;ans^=res;}else{int res=getnxt(x);lst=res;ans^=res;}}printf("%d\n",ans);return 0; } /**/splay
看好幾篇博客說(shuō)splay在區(qū)間問(wèn)題的功能更強(qiáng)大,所以也學(xué)習(xí)了splay
最后實(shí)在de不出來(lái)bug還是動(dòng)用了減法原理
累死窩了qwq
這個(gè)東西真的好難debug
但是決定以后就用它了awa
當(dāng)然要用強(qiáng)的啦
這個(gè)東西好好講講
開(kāi)點(diǎn)
所有的點(diǎn)都是開(kāi)出來(lái)的
開(kāi)點(diǎn)還是比較正常
int New(int v,int fa){val[++tot]=v;f[tot]=fa;ch[tot][0]=ch[tot][1]=0;siz[tot]=cnt[tot]=1;return tot; }旋轉(zhuǎn)
它也是旋轉(zhuǎn)完成的,不能沒(méi)有它
和treap一樣啦
防止寫(xiě)錯(cuò),總體的改變可以分三對(duì)
splay
splay怎么能不splay呢
所以我們現(xiàn)在講講splay的splay部分(停止扯淡)
splay總的來(lái)說(shuō)就是把一個(gè)結(jié)點(diǎn)不停轉(zhuǎn)轉(zhuǎn)轉(zhuǎn)
一直轉(zhuǎn)到根的地方
沿途長(zhǎng)鏈死光光
從而保證復(fù)雜度的正確性
這也是splay的精髓所在
而且跳到根也便利了我們其他的操作
有一個(gè)很關(guān)鍵的細(xì)節(jié)
就是當(dāng)父親和自己相對(duì)于各自父節(jié)點(diǎn)的方向同向時(shí)
必須要先轉(zhuǎn)父親
不然就無(wú)法達(dá)到消鏈的目的
這個(gè)可以自己畫(huà)畫(huà)圖理解
(我看別人題解畫(huà)的天花亂墜,最后還是自己畫(huà)圖才明白的)
代碼極為簡(jiǎn)潔
插入
開(kāi)始干正事了
找到應(yīng)該加點(diǎn)的位置開(kāi)點(diǎn)
然后splay一下
注意pushup!
查找第k大元素
這個(gè)很好寫(xiě)
理解起來(lái)應(yīng)該也不難
查找給定元素的排名
這個(gè)也沒(méi)有太大的難度
(盡管我de了一年多之后發(fā)現(xiàn)就是這里寫(xiě)掛的)
為了后面刪除元素的遍歷我們找到這個(gè)元素后splay一下
前驅(qū)&后繼
這里是找的根的前驅(qū)(后繼)
找給定值的前驅(qū)(后繼)的話就先insert進(jìn)去,它自動(dòng)splay到根,然后再求就行了
刪除
這個(gè)是重點(diǎn)
首先把刪除的元素利用前面現(xiàn)成的findrank提到根上
有副本就直接刪
否則看它的兒子情況
啥都沒(méi)有就直接變空樹(shù)了
只有一個(gè)就把那個(gè)兒子當(dāng)成根
如果兩個(gè)兒子都有就考慮把根的前驅(qū)提上來(lái)
因?yàn)槭乔膀?qū),所以它在到x之前一定沒(méi)有右兒子
也就是這樣:
再轉(zhuǎn)一下:
注意到待刪元素一定沒(méi)有左兒子
因此我們可以把B直接接到pre上達(dá)到刪除的目的
也就是:
這樣就ok啦
void del(int v){findrank(v);if(cnt[r]>1) {cnt[r]--;return;}else if(!ch[r][0]&&!ch[r][1]){r=0;return;}else if(!ch[r][0]){int temp=r;r=ch[r][1];f[r]=0;return;}else if(!ch[r][1]){int temp=r;r=ch[r][0];f[r]=0;return;}int temp=ch[r][1],pre=findpre(),oldr=r;splay(pre);ch[r][1]=temp;f[temp]=r;pushup(r); }完整代碼
#include<bits/stdc++.h> using namespace std; const int N=1e5+100; #define ll long long int n,m,k; int x,y; int cnt[N],ch[N][2],val[N],siz[N],tot,r; int f[N]; int New(int v,int fa){val[++tot]=v;f[tot]=fa;ch[tot][0]=ch[tot][1]=0;siz[tot]=cnt[tot]=1;return tot; } void pushup(int o){if(o) siz[o]=siz[ch[o][0]]+siz[ch[o][1]]+cnt[o]; } void build(){r=New(-2e9,0);ch[1][1]=New(2e9,r);pushup(r); } int getwhich(int x){return ch[f[x]][1]==x; } void rotate(int x){int fa=f[x],gfa=f[fa];int k=getwhich(x);int temp=ch[x][k^1];f[temp]=fa;ch[fa][k]=temp;f[x]=gfa;if(gfa) ch[gfa][ch[gfa][1]==fa]=x;f[fa]=x;ch[x][k^1]=fa;pushup(x);pushup(fa); } void splay(int x){for(int fa;fa=f[x];rotate(x)){if(f[fa]) rotate((getwhich(fa)==getwhich(x))?fa:x);}r=x; } void insert(int v){if(!r){r=New(v,0);return;}int now=r,fa=0;while(1){if(val[now]==v){cnt[now]++;pushup(now);pushup(fa);splay(now);break;}fa=now;now=ch[now][v>val[now]];if(!now){ch[fa][v>val[fa]]=New(v,fa);pushup(fa); // printf("\ninsert:(pre)\n"); // print();splay(tot); // printf("\ninsert:(after)\n"); // print();break;}} } int findnth(int k){int now=r;while(1){if(siz[ch[now][0]]>=k) now=ch[now][0];else if(siz[ch[now][0]]+cnt[now]>=k) return val[now];else{k-=siz[ch[now][0]]+cnt[now];now=ch[now][1];}} } int findrank(int x){int now=r,ans=0;while(1){if(!now) return ans+1;if(val[now]>x) now=ch[now][0];else if(val[now]==x){ans+=siz[ch[now][0]];splay(now);return ans+1;}else{ans+=siz[ch[now][0]]+cnt[now];now=ch[now][1];}} } int findpre(){int now=ch[r][0];while(ch[now][1]) now=ch[now][1];return now; } int findnxt(){int now=ch[r][1];while(ch[now][0]) now=ch[now][0];return now; } void del(int v){findrank(v);if(cnt[r]>1) {cnt[r]--;return;}else if(!ch[r][0]&&!ch[r][1]){r=0;return;}else if(!ch[r][0]){int temp=r;r=ch[r][1];f[r]=0;return;}else if(!ch[r][1]){int temp=r;r=ch[r][0];f[r]=0;return;}int temp=ch[r][1],pre=findpre(),oldr=r;splay(pre);ch[r][1]=temp;f[temp]=r;pushup(r); } int main(){scanf("%d",&n);int flag;for(int i=1;i<=n;i++){scanf("%d%d",&flag,&x);if(flag==1) insert(x);else if(flag==2) del(x);else if(flag==3) printf("%d\n",findrank(x));else if(flag==4) printf("%d\n",findnth(x));else if(flag==5){insert(x);printf("%d\n",val[findpre()]);del(x);}else{insert(x);printf("%d\n",val[findnxt()]);del(x);} // print();}return 0; } /**/練習(xí)總結(jié)
傳送門(mén)
總結(jié)
以上是生活随笔為你收集整理的模板:二叉搜索树平衡树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 恍然大悟的悟的意思 恍然大悟的悟的意思简
- 下一篇: 阶段总结:8.09-8.18 十日模拟