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