日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

模板:二叉搜索树平衡树

發(fā)布時(shí)間:2023/12/3 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 模板:二叉搜索树平衡树 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 前言
  • 二叉搜索樹(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ì)

  • x與gfa的父子關(guān)系
  • fa與x的父子關(guān)系
  • x的異向兒子與fa的父子關(guān)系
  • 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); }

    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)潔

    void splay(int x){for(int fa;fa=f[x];rotate(x)){if(f[fa]) rotate((getwhich(fa)==getwhich(x))?fa:x);}r=x; }

    插入

    開(kāi)始干正事了
    找到應(yīng)該加點(diǎn)的位置開(kāi)點(diǎn)
    然后splay一下
    注意pushup!

    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;}} }

    查找第k大元素

    這個(gè)很好寫(xiě)
    理解起來(lái)應(yīng)該也不難

    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];}} }

    查找給定元素的排名

    這個(gè)也沒(méi)有太大的難度
    (盡管我de了一年多之后發(fā)現(xiàn)就是這里寫(xiě)掛的)
    為了后面刪除元素的遍歷我們找到這個(gè)元素后splay一下

    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]];//記錄一下車禍現(xiàn)場(chǎng)splay(now);return ans+1;}else{ans+=siz[ch[now][0]]+cnt[now];now=ch[now][1];}} }

    前驅(qū)&后繼

    這里是找的根的前驅(qū)(后繼)
    找給定值的前驅(qū)(后繼)的話就先insert進(jìn)去,它自動(dòng)splay到根,然后再求就行了

    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; }

    刪除

    這個(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)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。