1. 程式人生 > >平衡樹學習筆記(2)-------Treap

平衡樹學習筆記(2)-------Treap

Treap

上一篇:平衡樹學習筆記(1)-------簡介

Treap是一個玄學的平衡樹

為什麼說它玄學呢? 還記得上一節說過每個平衡樹都有自己的平衡方式嗎?

沒錯,它平衡的方式是。。。。。。rand!!!!

注意,Treap是不依靠旋轉平衡的!!

我認為它的思想是最好理解的,程式碼也簡潔易懂(雖然慢了點)

而且靈活性較高,尤其是平衡樹合併qwq

洛谷P3369普通平衡樹跑了600多ms

\(\color{#9900ff}{基本操作}\)

1、split

split,顧名思義,就是分裂的意思

作用是把一棵樹分裂成為兩棵樹

但是總不能隨便分裂吧。。

因此,其內有4個引數

split(a,b,c,val)代表把以a為根的樹分裂,分裂後的兩棵樹樹根分別為b,c,保證樹b上的所有節點權值\(\leq val\),樹c上的所有點權\(\geq val\)

要獲得分裂後樹的兩根,所以a,b要傳址,即split(node *a,node *&b,node *&c,int val)

放上程式碼

void split(nod o,nod &a,nod &b,int val)
{
    if(o==null)   //遞迴邊界,當前位置為空,分裂後當然都為空
    {       
        a=b=null;
        return;
    }
    if(o->val<=val) a=o,split(o->ch[1],a->ch[1],b,val);  
    //小於等於val的要在a裡面,所以先直接讓a=o,為什麼呢
    //既然o->val<=val,顯然o的左子樹所有值都小於val,因此這些點都是a的
    //但是我們不能保證o右子樹的所有點<=val,因此遞歸向下來構建a的右子樹,本層對b無貢獻,所以還是b
    else b=o,split(o->ch[0],a,b->ch[0],val);
    //同上
    o->upd();
    //別忘了維護性質
    //為什麼要維護性質呢
    //其實那個if else應該這麼寫
    /*
    if(o->val<=val) a=o,split(o->ch[1],a->ch[1],b,val),a->upd();
    else b=o,split(o->ch[0],a,b->ch[0],val),b->upd();
    */
    //a或b樹會改變,所以要維護
    //但其實已經讓a=o或者b=o了,所以直接維護o即可
}

2、merge

merge,顧名思義,就是合併的意思

作用是把兩棵樹合併成為兩棵樹(有分裂就得有合併唄)

這回就是隨便合併了。。。。。。

怎麼隨便合併呢? 沒錯,rand!

其內有3個引數

merge(a,b,c)代表把以b,c為根的兩棵樹合併,合併後的樹樹根為a

要獲得合併後樹根,所以a要傳址,即merge(node *&a,node *b,node *c)

放上程式碼

void merge(nod &o,nod a,nod b)
{
    if(a==null||b==null)   //有一個為空,則等於另一個(如果另一個也是空其實就是空了)
    {
        o=a==null? b:a; //為不空的那個
        return;
    }
    if(a->key<=b->key) o=a,merge(o->ch[1],a->ch[1],b);   
    //這個key就是rand,不解釋
    //方法跟split差不多,這樣也好記qwq
    //反正瞎搞總比不搞弄成一條鏈強。。。。。。
    //這樣就可以使極端情況儘量少
    else o=b,merge(o->ch[0],a,b->ch[0]);
    o->upd();
    //別忘了維護性質
}

至此,基本操作已經完成(是不是很簡單?)

\(\color{#9900ff}{其它操作}\)

1、插入

既然有了基本操作,肯定是不能暴力插了。。。

其實每個操作都要用到基本操作的(可見其重要性)

inline void ins(int val)
{
    nod x=null,y=null;
    //定義兩個空節點
    //作用:一會分裂的時候作為兩棵樹的根,起一個承接作用
    nod z=newnode(val);
    //定義要插入的節點
    split(root,x,y,val);
    //因為要保證平衡樹的性質,所以插入的位置必須要合適
    //我們把所有<=val的點都分給x,剩下的分給y
    //這樣原來以root為根的數分成了兩個
    //我們要把z插進去
    //怎麼插♂呢
    //可以把z一個點看成一棵樹
    //直接暴力合併就行了
    merge(x,x,z);
    merge(root,x,y);
}
//沒了?
//沒了!

2、刪除

刪除其實也很簡單,幾乎就是圍繞split,merge暴力操作

inline void del(int val)
{
    nod x=null,y=null,z=null;
    split(root,x,y,val);
    split(x,x,z,val-1);
    //樹x的所有點權都小於val
    //樹y的所有點權都大於val
    //綜上,樹z的點權等於val
    //所以。。。。。。
    merge(z,z->ch[0],z->ch[1]);
    //我們只刪除一個val,所以剩下的要合併,別忘了
    merge(x,x,z);
    merge(root,x,y);
    //把分崩離析(<----瞎用成語)的樹恢復原狀
}

3、查詢數x的排名

排名,可以理解為比x小的數的個數+1(理解一下,這是解決此操作的關鍵)

所以我們要找到比x小的數的個數

inline int rnk(int val)
{
    nod x=null,y=null;
    split(root,x,y,val-1);
    //把所有小於val的點分走
    int t=x->siz+1;
    //x作為所有合法點的根,他的大小不正是我們要找的比val小的數的個數嗎?
    //加一就是排名!
    merge(root,x,y);
    //不要過於興奮,你的樹還沒有合併!!!!
    return t;
}

4、查詢第k大的數

這個是唯一不借助基本操作的操作

inline nod kth(nod o,int rank)
{
    //第k大不就是排名為k的數麼
    //這不就是操作3的逆操作嗎
    while(o->ch[0]->siz+1!=rank)                         //暴力找。。。
    if(o->ch[0]->siz>=rank) o=o->ch[0];               //說明那個數在左子樹
    else rank-=o->ch[0]->siz+1,o=o->ch[1];  
    //那個數在右子樹,注意,這裡要減去左子樹大小和自己,因為到了下面,上面比自己小的就統計不到了,
    //反正都是比自己小的,直接減去最好
    //理解一下
    return o;
}

5、前驅

inline nod pre(int val)
{
    nod x=null,y=null;
    split(root,x,y,val-1);
    //分離所有小於y的數
    nod z=kth(x,x->siz);
    //既然pre為小於val的數中最大的一個,我們就找x樹中的最大的那個不就行了?
    merge(root,x,y);
    //別忘了合併
    return z;
}

6、後繼

inline nod nxt(int val)
{
    //跟上面只是稍稍有點不同而已
    nod x=null,y=null;
    split(root,x,y,val);
    //把所有小於等於val的點都分走,注意這裡可以取等號!
    //那麼y中的點都大於val
    //在其中找最小的
    nod z=kth(y,1);
    merge(root,x,y);
    //別忘合併
    return z;
}

沒了。。。。。。

Treap就沒了。。。。。。

放上完整程式碼

#include<cstdio>
#include<queue>
#include<vector>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cctype>
#include<cmath>
#define _ 0
#define LL long long
#define Space putchar(' ')
#define Enter putchar('\n')
#define fuu(x,y,z) for(int x=(y);x<=(z);x++)
#define fu(x,y,z)  for(int x=(y);x<(z);x++)
#define fdd(x,y,z) for(int x=(y);x>=(z);x--)
#define fd(x,y,z)  for(int x=(y);x>(z);x--)
#define mem(x,y)   memset(x,y,sizeof(x))
const int max=1e5+5;
struct node
{
    node *ch[2];
    int siz,val,key;
    node() {siz=val=key=0;}
    inline void upd() {siz=ch[0]->siz+ch[1]->siz+1;}
}s[max];
typedef node* nod;
nod root;
nod null;
int cnt;
int n;
inline nod newnode(int k)
{
    cnt++;
    s[cnt].ch[0]=s[cnt].ch[1]=null;
    s[cnt].key=rand(); s[cnt].siz=1; s[cnt].val=k;
    return &s[cnt];
}
void split(nod o,nod &a,nod &b,int val)
{
    if(o==null)
    {       
        a=b=null;
        return;
    }
    if(o->val<=val) a=o,split(o->ch[1],a->ch[1],b,val),a->upd();
    else b=o,split(o->ch[0],a,b->ch[0],val),b->upd();
}
void merge(nod &o,nod a,nod b)
{
    if(a==null||b==null)
    {
        o=a==null? b:a;
        return;
    }
    if(a->key<=b->key) o=a,merge(o->ch[1],a->ch[1],b);
    else o=b,merge(o->ch[0],a,b->ch[0]);
    o->upd();
}
inline nod kth(nod o,int rank)
{
    while(o->ch[0]->siz+1!=rank)
    if(o->ch[0]->siz>=rank) o=o->ch[0];
    else rank-=o->ch[0]->siz+1,o=o->ch[1];
    return o;
}
inline void ins(int val)
{
    nod x=null,y=null;
    nod z=newnode(val);
    split(root,x,y,val);
    merge(x,x,z);
    merge(root,x,y);
}
inline void del(int val)
{
    nod x=null,y=null,z=null;
    split(root,x,y,val);
    split(x,x,z,val-1);
    merge(z,z->ch[0],z->ch[1]);
    merge(x,x,z);
    merge(root,x,y);
}
inline int rnk(int val)
{
    nod x=null,y=null;
    split(root,x,y,val-1);
    int t=x->siz+1;
    merge(root,x,y);
    return t;
}
inline nod pre(int val)
{
    nod x=null,y=null;
    split(root,x,y,val-1);
    nod z=kth(x,x->siz);
    merge(root,x,y);
    return z;
}
inline nod nxt(int val)
{
    nod x=null,y=null;
    split(root,x,y,val);
    nod z=kth(y,1);
    merge(root,x,y);
    return z;
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin>>n;
    null=new node(); null->ch[0]=null->ch[1]=null;
    root=null;
    ins(0x7fffffff);
    int flag,x; 
    while(n--)
    {
        std::cin>>flag>>x;
        if(flag==1) ins(x);
        if(flag==2) del(x);
        if(flag==3) std::cout<<rnk(x)<<"\n";
        if(flag==4) std::cout<<kth(root,x)->val<<"\n";
        if(flag==5) std::cout<<pre(x)->val<<"\n";
        if(flag==6) std::cout<<nxt(x)->val<<"\n";
    }
    return ~~(0^_^0);
}