普通平衡樹Treap(含旋轉)學習筆記

淺談普通平衡樹Treap

平衡樹,Treap=Tree+heap這是一個很形象的東西c++

咱們要維護一棵樹,它知足堆的性質和二叉查找樹的性質(BST),這樣的二叉樹咱們叫作平衡樹數據結構

而且平衡樹它的結構是接近於比較均衡的。ide

考慮Treap解決的問題:插入,刪除,排名(排名爲x的數,數x的排名)、前驅和後繼函數

這裏的英文函數名分別定義爲insert(插入) erase(刪除),rank(求數x的排名),find(求排名是x的數),pre(x的前驅),nex(x的後繼)spa

1Treap節點的定義和意義
code

對於一個Treap的節點咱們定義以下變量size\val\cnt\key\ch[0]\ch[1]blog

分別解釋一下他們的含義:get

數的個數和數的種類是不一樣的概念!it

size:和樹鏈剖分中size[]的含義同樣這裏的含義是節點u如下含u有多少數的個數io

val:儲存在節點u中,相同元素的值

cnt:儲存在節點u中,相同元素的數的個數

key:一個隨機數在平衡樹中要求父親的key值,小於兩個兒子的key值用來保證樹的平衡

ch[0]:左兒子

ch[1]:右兒子

20 rotate函數的解釋*

(以右旋爲例)

這是原來的樹:

咱們要把D點(是B的左兒子)右旋到根節點B

首先把D點和右節點G的邊斷,D和他父親的邊斷。

然而若是咱們把D變成子樹的根的話由BST的性質得D<B,因此B必定是D的右子節點。

等會!那G怎麼辦?

分析G的大小: B>G>D恰好能夠放在B的左子樹,而後把BDE這棵子樹整個的連到D上剛恰好吧!

 

 其實真的比較清楚,就這麼把左端點轉到根節點了!

注意到咱們這裏是按照優先級的大小來肯定轉的方向從而肯定出一個Heap(大根堆)的。

啊啊啊啊好容易找到兩張動圖GIF:

左旋:(右邊兒子S上去,父親E到左邊)

右旋:(左邊兒子E上去,父親S到右邊)

這樣能夠保證樹的形態隨機!

void rotate(int &x)//這是右旋
{ int son=t[x].ch[0]; //son一直都是P 
    t[x].ch[0]=t[son].ch[1]; //Q的左節點爲B
    t[son].ch[1]=x; //P的右節點是Q 
    up(x); up(x=son);//因爲位置變化size和cnt都變化 
}

 請讀者按照上圖理解右旋的相關內容,並嘗試寫出左旋的代碼:

 

void rotate(int &x) { int son=t[x].ch[1];//son一直都是Q 
    t[x].ch[1]=t[son].ch[0];//P的右節點爲B 
    t[son].ch[0]=x;//Q的左節點是P 
    up(x); up(x=son);//更新 
}

 

由此咱們獲得旋轉通常寫法:(d表明那個子節點(0左1右)想向上)

 

void rotate(int &x,int d)//包含左旋右旋d=1左旋(右子向上)d=0右旋(左子向上)
{ int son=t[x].ch[d]; t[x].ch[d]=t[son].ch[d^1]; t[son].ch[d^1]=x; up(x); up(x=son); }

30其餘函數略談

A.insert

void insert(int &x,int val)//插入節點
{ if (!x) {  //此節點爲空新開一個
        x=++tot; t[x].size=t[x].cnt=1; t[x].val=val; t[x].key=rand(); return; } t[x].size++; //進過一次x必定在x的子樹上因此size要++
    if (t[x].val==val) { t[x].cnt++; return;} //等於就直接插入
    int d=t[x].val<val; //當前節點的val小於要求的val往大的搜
    insert(t[x].ch[d],val); //走對應的兒子
    if (t[x].key>t[t[x].ch[d]].key) rotate(x,d); //必須知足父節點的key大於兩個兒子
}

B.erase

void erase(int &x,int val) { if (!x) return;//節點爲空跳過
    if (t[x].val==val) { //找到了
        if (t[x].cnt>1) { t[x].cnt--; t[x].size--; return;} //大於一不刪點
        int d=t[ls].key>t[rs].key; //刪點
        if (ls==0||rs==0) x=ls+rs; //往有孩子的一個地方走
        else rotate(x,d),erase(x,val); //先轉一轉把空節點放在下面,而後最終會被放在最底層
    } else t[x].size--,erase(t[x].ch[t[x].val<val],val); //通過出必定在x子樹中因此x的size要--,而後往二叉查找方向搜
}

C.rank和find

int rank(int x,int val){//找val的rank值
    if (!x) return 0; //沒有返回0
    if (t[x].val==val) return t[ls].size+1; //找到當前根就是x那麼就是比他小的數的數目size+1(排名在他們以後)
    if (t[x].val>val) return rank(ls,val);//當前大了,那麼往小的找
    return t[x].cnt+t[ls].size+rank(rs,val);  //當前小了說明左兒子的size和根節點的cnt都是比val小的要加上數的個數,而後往大的搜
} int find(int rt,int k) //求rank=k的值是多少
{ int x=rt; //從根開始
    while (1) { if (k<=t[ls].size) x=ls; //當前的k比左子樹的元素個數少了那麼必定往左子樹搜
        else if (k>t[x].cnt+t[ls].size) k-=t[x].cnt+t[ls].size,x=rs;//若是當前的k比左子樹數的個數和根節點的cnt之和還大那麼就是在右子樹裏,減掉(t[x].cnt+t[ls].size)用新的k迭代(從另一個根搜)
        else return t[x].val;//不然就是找到了return就行
 } 

D.pre和nex

int pre(int x,int val) { if (!x) return -0x7f7f7f7f7f; //避免越界不讓更新
    if (t[x].val>=val) return pre(ls,val); //等號千萬別拉下
    return max(pre(rs,val),t[x].val);//以防搜不到
} int nex(int x,int val) { if (!x) return 0x7f7f7f7f7f;//避免越界不讓更新
    if (t[x].val<=val) return nex(rs,val);//等號千萬別拉下
    return min(nex(ls,val),t[x].val);//以防搜不到
}

40模板題目

P3369 【模板】普通平衡樹

題目描述

您須要寫一種數據結構(可參考題目標題),來維護一些數,其中須要提供如下操做:

  1. 插入x數
  2. 刪除x數(如有多個相同的數,因只刪除一個)
  3. 查詢x數的排名(排名定義爲比當前數小的數的個數+1。如有多個相同的數,因輸出最小的排名)
  4. 查詢排名爲x的數
  5. x的前驅(前驅定義爲小於x,且最大的數)
  6. x的後繼(後繼定義爲大於x,且最小的數)

輸入輸出格式

輸入格式:

第一行爲n,表示操做的個數,下面n行每行有兩個數opt和x,opt表示操做的序號( 1opt6 )

輸出格式:

對於操做3,4,5,6每行輸出一個數,表示對應答案

輸入輸出樣例

輸入樣例#1: 
10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598
輸出樣例#1:
106465 84185 492737
輸入樣例#2: 
50
1 577793
1 408221
1 880861
2 408221
1 460353
1 223489
6 577713
4 2
5 889905
2 880861
1 100033
1 73956
1 22575
5 583761
6 571549
1 812645
4 3
1 643621
1 451623
6 14895
1 556691
4 1
1 225789
2 22575
1 632329
3 73956
1 316785
5 101413
4 11
5 639414
6 636353
1 272382
1 434049
2 643621
1 99617
2 577793
1 921581
1 894033
3 223489
1 767367
3 272382
1 642721
1 272033
3 632329
1 737721
1 864513
5 746457
1 877545
1 51097
1 484817
View Code
輸出樣例#2:
577793
460353
880861
577793
577793
100033
22575
22575
1
100033
643621
632329
643621
4
6
13
737721
View Code

說明

時空限制:1000ms,128M

1.n的數據範圍: n100000

2.每一個數的數據範圍:[10^7,10^7]

# include <bits/stdc++.h>
using namespace std; const int MAXN=100005; int root=0;int tot=0; inline int read() { int X=0,w=0;char c=0; while (!(c>='0'&&c<='9')) w|=c=='-',c=getchar(); while (c>='0'&&c<='9') X=(X<<1)+(X<<3)+(c^48),c=getchar(); return w?-X:X; } inline void po(int x){ if (x<0) { putchar('-');x=-x;} if (x>9) po(x/10); putchar('0'+x%10); } inline void print(int x){ po(x);putchar('\n');} struct Treap{ int size,val,cnt,key,ch[2];}t[MAXN]; #define ls t[x].ch[0]
#define rs t[x].ch[1]
void up(int x){t[x].size=t[x].cnt+t[ls].size+t[rs].size;} void rotate(int &x,int d) { int son=t[x].ch[d]; t[x].ch[d]=t[son].ch[d^1]; t[son].ch[d^1]=x; up(x); up(x=son); } void insert(int &x,int val) { if (!x) { x=++tot; t[x].size=t[x].cnt=1; t[x].val=val; t[x].key=rand(); return; } t[x].size++; if (t[x].val==val) { t[x].cnt++; return;} int d=t[x].val<val; insert(t[x].ch[d],val); if (t[x].key>t[t[x].ch[d]].key) rotate(x,d); } void erase(int &x,int val) { if (!x) return; if (t[x].val==val) { if (t[x].cnt>1) { t[x].cnt--; t[x].size--; return;} int d=t[ls].key>t[rs].key; if (ls==0||rs==0) x=ls+rs; else rotate(x,d),erase(x,val); } else t[x].size--,erase(t[x].ch[t[x].val<val],val); } inline int rank(int x,int val){ if (!x) return 0; if (t[x].val==val) return t[ls].size+1; if (t[x].val>val) return rank(ls,val); return t[x].cnt+t[ls].size+rank(rs,val); } int find(int rt,int k) { int x=rt; while (1) { if (k<=t[ls].size) x=ls; else if (k>t[x].cnt+t[ls].size) k-=t[x].cnt+t[ls].size,x=rs; else return t[x].val; } } inline int pre(int x,int val) { if (!x) return -0x7f7f7f7f7f; if (t[x].val>=val) return pre(ls,val); return max(pre(rs,val),t[x].val); } int nex(int x,int val) { if (!x) return 0x7f7f7f7f7f; if (t[x].val<=val) return nex(rs,val); return min(nex(ls,val),t[x].val); } int main() { srand(time(NULL)*10007); int m=read(),opt,x; tot=0; while (m--) { opt=read();x=read(); switch (opt) { case 1:insert(root,x);break; case 2:erase(root,x);break; case 3:print(rank(root,x));break; case 4:print(find(root,x));break; case 5:print(pre(root,x));break; case 6:print(nex(root,x));break; } } return 0; }

50注意點(容易碼錯的地方)

  • 在全局定義root變量初始化爲0
  • rank函數請把return t[L].size+t[x].cnt+rank(R,val);語句放在最後不要放在中間!

60STL平衡樹瞭解下

# include <bits/stdc++.h> # define int long long
using namespace std; int n; struct STL_Treap{ vector<int>a; void insert(int x) { a.insert(lower_bound(a.begin(),a.end(),x),x);} void erase(int x) {a.erase(lower_bound(a.begin(),a.end(),x));} int rank(int x) {return lower_bound(a.begin(),a.end(),x)-a.begin()+1;} int kth(int x){return a[x-1];} int pre(int x) {return *(--lower_bound(a.begin(),a.end(),x));} int nxt(int x){return *upper_bound(a.begin(),a.end(),x);} }treap; signed main() { scanf("%lld",&n); for (int i=1;i<=n;i++) { int a,b; scanf("%lld%lld",&a,&b); switch (a) { case 1ll:treap.insert(b);break; case 2ll:treap.erase(b);break; case 3ll:cout<<treap.rank(b)<<'\n';break; case 4ll:cout<<treap.kth(b)<<'\n';break; case 5ll:cout<<treap.pre(b)<<'\n';break; case 6ll:cout<<treap.nxt(b)<<'\n';break; } } return 0; }
相關文章
相關標籤/搜索