平衡樹學習筆記(2)-------Treap
阿新 • • 發佈:2018-11-24
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);
}