關於Treap學習總結


Treap 學習筆記

Treap 簡介

  Treap 是一種二叉查找樹。它的結構同時滿足二叉查找樹(Tree)與堆(Heap)的性質,因此得名。Treap的原理是為每一個節點賦一個隨機值使其滿足堆的性質,保證了樹高期望 O(log2n) ,從而保證了時間復雜度。 
  Treap 是一種高效的平衡樹算法,在常數大小與代碼復雜度上好於 Splay。

Treap 的基本操作

  現在以 BZOJ 3224 普通平衡樹為模板題,詳細討論 Treap 的基本操作。

1.基本結構

  在一般情況下,Treap 的節點需要存儲它的左右兒子,子樹大小,節點中相同元素的數量(如果沒有可以默認為1),自身信息及隨機數的值。

struct node{
    int l, r, v, siz, rnd, ct;
}d[1000005];

其中 l 為左兒子節點編號, r 為右兒子節點編號, v 為節點數值, siz 為子樹大小, rnd 為節點的隨機值, ct為該節點數值的出現次數(目的為將所有數值相同的點合為一個)。

2.關於隨機值

  隨機值由 rand() 函數生成, 考慮到 <cstdlib> 庫中的 rand() 速度較慢,所以在卡常數的時候建議手寫 rand() 函數。

inline int rand(){
    static int seed = 2333;
    return seed = (int)((((seed ^ 998244353) + 19260817ll) * 19890604ll) % 1000000007);
}

其中 seed 為隨機種子,可以隨便填寫。

3.節點信息更新

  節點信息更新由 update() 函數實現。在每次產生節點關系的修改后,需要更新節點信息(最基本的子樹大小,以及你要維護的其他內容)。 
  時間復雜度 O(1) 。

inline void update(int k){
    d[k].siz = d[lc].siz + d[rc].siz + d[k].ct;
}

  

4.「重要」左旋與右旋

  左旋與右旋是 Treap 的核心操作,也是 Treap 動態保持樹的深度的關鍵,其目的為維護 Treap 堆的性質。 
  下面的圖片可以讓你更好的理解左旋與右旋:

  這里寫圖片描述

  下面具體介紹左旋與右旋操作。左旋與右旋均為變更操作節點與其兩個兒子的相對位置的操作。 
  「左旋」為將作兒子節點代替根節點的位置, 根節點相應的成為左兒子節點的右兒子(滿足二叉搜索樹的性質)。相應的,之前左兒子節點的右兒子應轉移至之前根節點的左兒子。此時,只有之前的根節點與左兒子節點的 siz 發生了變化。所以要 update() 這兩個節點。 
  「右旋」類似於「左旋」,將左右關系相反即可。 
  時間復雜度 O(1) 。 

void rturn(int &k){ //右旋
    int t = d[k].l; d[k].l = d[t].r; d[t].r = k;
    d[t].siz = d[k].siz; update(k); k = t;
}

void lturn(int &k){ //左旋
    int t = d[k].r; d[k].r = d[t].l; d[t].l = k;
    d[t].siz = d[k].siz; update(k); k = t;
}

 

5.節點的插入與刪除

  節點的插入與刪除是 Treap 的基本功能之一。 
  「節點的插入」是一個遞歸的過程,我們從根節點開始,逐個判斷當前節點的值與插入值的大小關系。如果插入值小於當前節點值,則遞歸至左兒子;大於則遞歸至右兒子;

  相等則直接在把當前節點數值的出現次數 +1 ,跳出循環即可。如果當前訪問到了一個空節點,則初始化新節點,將其加入到 Treap 的當前位置。 
  「節點的刪除」同樣是一個遞歸的過程,不過需要討論多種情況: 
  如果插入值小於當前節點值,則遞歸至左兒子;大於則遞歸至右兒子。 
  如果插入值等於當前節點值: 
    若當前節點數值的出現次數大於 1 ,則減一; 
    若當前節點數值的出現次數等於於 1 : 
      若當前節點沒有左兒子與右兒子,則直接刪除該節點(置 0); 
      若當前節點沒有左兒子或右兒子,則將左兒子或右兒子替代該節點; 
      若當前節點有左兒子與右兒子,則不斷旋轉 當前節點,並走到當前節點新的對應位置,直到沒有左兒子或右兒子為止。 
  時間復雜度均為 O(log2n) 。 
  具體實現代碼如下:

 1 void ins(int &p,int x)
 2 {
 3     if (p==0)
 4     {
 5         p=++sz;
 6         tr[p].siz=tr[p].ct=1,tr[p].val=x,tr[p].rnd=rand();
 7         return;
 8     }
 9     tr[p].siz++;
10     if (tr[p].val==x) tr[p].ct++;
11     else if (x>tr[p].val)
12     {
13         ins(tr[p].r,x);
14         if (tr[rs].rnd<tr[p].rnd) lturn(p);
15     }else
16     {
17         ins(tr[p].l,x);
18         if (tr[ls].rnd<tr[p].rnd) rturn(p);
19     }
20 }
21 void del(int &p,int x)
22 {
23     if (p==0) return;
24     if (tr[p].val==x)
25     {
26         if (tr[p].ct>1) tr[p].ct--,tr[p].siz--;//如果有多個直接減一即可。
27         else
28         {
29             if (ls==0||rs==0) p=ls+rs;//單節點或者空的話直接兒子移上來或者刪去即可。
30             else if (tr[ls].rnd<tr[rs].rnd) rturn(p),del(p,x);
31             else lturn(p),del(p,x); 
32         }
33     }
34     else if (x>tr[p].val) tr[p].siz--,del(rs,x);
35     else tr[p].siz--,del(ls,x);
36 }

6.查詢數x的排名

  查詢數x的排名可以利用在二叉搜索樹上的相同方法實現。 
  具體思路為根據遞歸找到當前節點,並記錄小於這個節點的節點的數量(左子樹) 。 
  時間復雜度 O(log2n) 。 
  代碼實現如下:

1 int find_pm(int p,int x)
2 {
3     if (p==0) return 0;
4     if (tr[p].val==x) return tr[ls].siz+1;
5     if (x>tr[p].val) return tr[ls].siz+tr[p].ct+find_pm(rs,x);
6     else return find_pm(ls,x);
7 }

7.查詢排名為x的數

  查詢排名為x的數可以利用在二叉搜索樹上的相同方法實現。 
  具體思路為根據當前x來判斷該數在左子樹還是右子樹 。 
  時間復雜度 O(log2n) 。 
  代碼實現如下:

1 int find_hj(int p,int x)
2 {
3     if (p==0) return inf;
4     if (tr[p].val<=x) return find_hj(rs,x);
5     else return min(tr[p].val,find_hj(ls,x));
6 }

8.查詢數的前驅與后繼

  查詢數的前驅與后繼同樣可以遞歸實現。查前驅即為遞歸當前數,走到小於等於x的節點,並記錄其中最大的。后繼同理。 
  時間復雜度 O(log2n) 。 
  代碼實現如下:

  

 1 int find_qq(int p,int x)
 2 {
 3     if (p==0) return -inf;
 4     if (tr[p].val<x) return max(tr[p].val,find_qq(rs,x));
 5     else if (tr[p].val>=x) return find_qq(ls,x);
 6 }
 7 int find_hj(int p,int x)
 8 {
 9     if (p==0) return inf;
10     if (tr[p].val<=x) return find_hj(rs,x);
11     else return min(tr[p].val,find_hj(ls,x));
12 }

 

總體合並的模板由bzoj3224這題,這是一道模板題,裸的。

  1 #include<cstring>
  2 #include<cmath>
  3 #include<iostream>
  4 #include<algorithm>
  5 #include<cstdio>
  6 
  7 #define ls tr[p].l
  8 #define rs tr[p].r
  9 #define N 100007
 10 #define inf 2000000010
 11 using namespace std;
 12 inline int read()
 13 {
 14     int x=0,f=1;char ch=getchar();
 15     while(ch<'0'||ch>'9'){if (ch=='-')f=-1;ch=getchar();}
 16     while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
 17     return x*f;
 18 }
 19 
 20 int n,sz,rt,ans;
 21 struct Node
 22 {
 23     int l,r,val,siz,rnd,ct;//記錄左兒子,右兒子,點值,該子樹大小,隨機的值,該點值出現的次數。 
 24 }tr[N];//最多多少個節點,就開多少空間 
 25 
 26 inline int rand(){
 27     static int seed = 2333;
 28     return seed = (int)((((seed ^ 998244353) + 19260817ll) * 19890604ll) % 1000000007);
 29 }
 30 inline void update(int p)
 31 {
 32     tr[p].siz=tr[ls].siz+tr[rs].siz+tr[p].ct;
 33 }
 34 void lturn(int &p)
 35 {
 36     int t=tr[p].r;tr[p].r=tr[t].l;tr[t].l=p;
 37     tr[t].siz=tr[p].siz;update(p);p=t;
 38 }
 39 void rturn(int &p)
 40 {
 41     int t=tr[p].l;tr[p].l=tr[t].r;tr[t].r=p;
 42     tr[t].siz=tr[p].siz;update(p);p=t;
 43 }
 44 void ins(int &p,int x)
 45 {
 46     if (p==0)
 47     {
 48         p=++sz;
 49         tr[p].siz=tr[p].ct=1,tr[p].val=x,tr[p].rnd=rand();
 50         return;
 51     }
 52     tr[p].siz++;
 53     if (tr[p].val==x) tr[p].ct++;
 54     else if (x>tr[p].val)
 55     {
 56         ins(tr[p].r,x);
 57         if (tr[rs].rnd<tr[p].rnd) lturn(p);
 58     }else
 59     {
 60         ins(tr[p].l,x);
 61         if (tr[ls].rnd<tr[p].rnd) rturn(p);
 62     }
 63 }
 64 void del(int &p,int x)
 65 {
 66     if (p==0) return;
 67     if (tr[p].val==x)
 68     {
 69         if (tr[p].ct>1) tr[p].ct--,tr[p].siz--;//如果有多個直接減一即可。
 70         else
 71         {
 72             if (ls==0||rs==0) p=ls+rs;//單節點或者空的話直接兒子移上來或者刪去即可。
 73             else if (tr[ls].rnd<tr[rs].rnd) rturn(p),del(p,x);
 74             else lturn(p),del(p,x); 
 75         }
 76     }
 77     else if (x>tr[p].val) tr[p].siz--,del(rs,x);
 78     else tr[p].siz--,del(ls,x);
 79 }
 80 int find_pm(int p,int x)
 81 {
 82     if (p==0) return 0;
 83     if (tr[p].val==x) return tr[ls].siz+1;
 84     if (x>tr[p].val) return tr[ls].siz+tr[p].ct+find_pm(rs,x);
 85     else return find_pm(ls,x);
 86 }
 87 int find_sz(int p,int x)
 88 {
 89     if (p==0) return 0;
 90     if (x<=tr[ls].siz) return find_sz(ls,x);
 91     x-=tr[ls].siz;
 92     if (x<=tr[p].ct) return tr[p].val;
 93     x-=tr[p].ct;
 94     return find_sz(rs,x);
 95 }
 96 int find_qq(int p,int x)
 97 {
 98     if (p==0) return -inf;
 99     if (tr[p].val<x) return max(tr[p].val,find_qq(rs,x));
100     else if (tr[p].val>=x) return find_qq(ls,x);
101 }
102 int find_hj(int p,int x)
103 {
104     if (p==0) return inf;
105     if (tr[p].val<=x) return find_hj(rs,x);
106     else return min(tr[p].val,find_hj(ls,x));
107 }
108 int main()
109 {
110     n=read();
111     for (int i=1;i<=n;i++)
112     {
113         int flag=read(),x=read();
114         if (flag==1) ins(rt,x);
115         if (flag==2) del(rt,x);
116         if (flag==3) printf("%d\n",find_pm(rt,x));//尋找x的排名
117         if (flag==4) printf("%d\n",find_sz(rt,x));//尋找排名為x的數字 
118         if (flag==5) printf("%d\n",find_qq(rt,x));
119         if (flag==6) printf("%d\n",find_hj(rt,x));
120     }
121 }

特別感謝:http://blog.csdn.net/infinity_edge/article/details/78607724


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM