平衡樹初階——AVL平衡二叉查找樹
一、什么是二叉樹
1. 什么是樹。
計算機科學里面的樹本質是一個樹狀圖。樹首先是一個有向無環圖,由根節點指向子結點。但是不嚴格的說,我們也研究無向樹。所謂無向樹就是將有向樹的所有邊看成無向邊形成的樹狀圖。樹是一種遞歸的數據結構,所以我們研究樹也是按照遞歸的方式去研究的。
2.什么是二叉樹。
我們給出二叉樹的遞歸定義如下:
(1)空樹是一個二叉樹。
(2)單個節點是一個二叉樹。
(3)如果一棵樹中,以它的左右子節點為根形成的子樹都是二叉樹,那么這棵樹本身也是二叉樹。
二、BST
1.什么是二叉排序樹。
二叉排序樹是一種二叉樹,它滿足樹的中序遍歷是有序的。
2.BST(Binary Search Tree)。
二叉查找樹是一種最普通的二叉排序樹,一般稱作BST,也稱為B-樹。在這棵樹中,滿足在任意一個子樹中,都滿足左子樹 < 根節點 < 右子樹,即該樹的中序遍歷滿足從小到大排序的結果。
3.如何構造一個二叉排序樹?
很顯然,要想構造一個BST,就必須在插入節點時,滿足下面的原則:
(1)如果節點為空,則直接插入到該節點。
(2)如果節點不為空,且要插入的值小於等於當前節點的值,那么則將該節點插入到左子樹當中。
(3)如果節點不為空,且要插入的值大於當前節點的值,那么則將該節點插入到右子樹當中。
4.利用BST的性質對一個數組進行剃重。
由於BST有二叉排序樹的性質,我們可以利用這樣的性質對一個待定數組進行剃重。原理很簡單,只需要在插入的時候如果已經發現了相等的節點的話,那么則不進行插入即可。也就是說,只有該樹沒有的節點,我們才進行相應的插入操作。
三、BST的相關操作
1.建樹(createTree)
BST的建立是基於一個數組進行的,本質上是把數組中的數按順序插入的樹中。可以想象,,每插入一個數,平均時間復雜度為O(logn),所以建樹的平均時間復雜度為O(nlogn)。
2.查找某一個值d(searchTree)
如果我們需要在BST上查找一個值d,那么我們需要從根節點開始,按照下面的思路進行遞歸查詢:
(1)如果當前節點為空,則未找到,返回NULL。
(2)如果當前節點不為空,且當前節點的值等於d,那么則找到,返回當前節點。
(3)如果當前節點不為空,且當前節點的值大於d,那么則遞歸在左子樹中尋找。
(4)如果當前節點不為空,且當前節點的值小於d,那么則遞歸在右子樹中尋找。
可以想象,查找操作的平均時間復雜度為O(logn)
3.刪除一個值d(deleteTree)
如果我們想要刪除一個值d的節點,那么顯然首先需要找到該節點。如果沒有找到,則刪除操作失敗,如果找到,繼續下面的操作即可:
(1)如果找到的節點的右子樹為空,那么直接用該節點的左節點替換當前節點即可。
(2)如果找到的節點的右子樹不為空,且右子樹的左子樹不為空,則遞歸找該右子樹的左子樹。
(3)如果找到的節點的右子樹不為空,且右子樹的左子樹為空,則:
①如果找到的該節點的右節點為空,則返回當前節點,用這個節點去替換需要刪除的點即可。
②如果找到的該節點的右子樹不為空,則首先用該右子樹替換找到的節點,在用找到的節點替換需要刪除的節點即可。
顯然,刪除操作的平均時間復雜度為O(logn)
四、AVL平衡二叉查找樹
1.什么是平衡二叉樹。
平衡二叉樹是一種二叉排序樹,並且滿足樹中任意一個節點的左右子樹的高度保持平衡。
2.什么是AVL。
AVL是一種二叉查找樹,並且滿足樹中任意一個節點的左右子樹的高度差的絕對值小於等於1,即保持平衡系數不大於1。AVL也稱B-BST(Balanced - Binary Search Tree),而AVL的名稱只是與這種數據結構的作者有關。
3.引例:為什么會產生AVL
我們為什么需要研究AVL,換句話說,為什么我們要重視BST的平衡性能呢?我們看下面的一個例子。
我們用1,2,3,4,5,6,7,8,9來進行建樹。我們發現,這樣建樹的結果如下:
可以看出,這樣二叉樹實際上退化為了一個鏈表。在最壞的情況下,插入和刪除的時間復雜度都退化為了O(n)。
很顯然,樹的平衡性越好,這種退化越不明顯。所以為了保持BST的高效,我們研究AVL是必要的。
4.如何保持AVL的平衡?
既然要保持左右子樹高度差小於等於1,那么我們一定需要在節點里面定義一個新的屬性,用來記錄該節點為根的樹的高度。由於建樹的過程是遞歸的,所以樹的高度的更新也是遞歸完成的。通過更新高度,我們就可以知道什么時候左右子樹的高度差大於1了,這個時候產生了失衡。一旦某一個節點開始失衡,那么這個時候必須通過旋轉操作使二叉樹達到一個新的平衡。
五、AVL的相關操作
1.旋轉操作(rotateAvl)
如果在某一個時刻二叉樹發生了失衡,我們就需要對二叉樹進行相應的旋轉使得二叉樹重新達到平衡。這個旋轉並不是隨意的,我們還要保證BST的基本性質,那就是中序遍歷必須有序才行。
我們總結二叉樹失衡的原因,可以歸納為以下四種情況(其中圓形節點代表失衡有關的節點,方形節點代表子樹)
歸納后發現,對於情況(1)(2),都是由於左子樹高度大於右子樹高度形成的失衡。對於情況(3)(4)則是相反的情況。且情況(1)(3)互為鏡像,情況(2)(4)互為鏡像。所以我們只以(1)(2)種情況作為例子,(3)(4)情況的道理同出一轍。
對於情況(1),左子樹高度大於右子樹高度,而在左子樹中,依然是左子樹高度大於右子樹高度。對於這樣的情況,我們需要以1為根進行右轉(rightRotate),右轉的結果是,2變為新的根,而1則成為2的右節點,2原本的右子樹則成為了1的左子樹,即,如下圖:
對於情況(2),左子樹高度大於右子樹高度,而在左子樹中,左子樹高度小於右子樹高度。對於這樣的情況,我們需要首先需要以2(leftRotate)為根進行左轉,左轉的結果是3變為1的左子樹,而2變為3的左子樹,而3原本的左子樹成了2的右子樹。如下圖所示:
之后就轉化為了情況(1),故只需要在以1為根進行右轉即可。
通過這樣的旋轉操作,AVL重新達到了平衡。
這個旋轉操作的時間復雜度是O(1)的。
2.高度更新操作。
由於高度更新是遞歸進行的,所以我們選擇回溯的階段進行高度更新。而一個節點的高度應該是左子樹高度和右子樹高度的最大值再加1。
高度更新在整個AVL中是必要的,不管建樹過程中,還是插入操作,或者是刪除操作中,我們都需要時時刻刻對高度進行更新,只有正確的更新高度,才能判斷二叉樹是否失衡。而一旦失衡,我們就需要進行上述的旋轉操作,這些是相輔相承的。
高度更新的時間復雜度也是O(1)的。
3.插入操作(insertAvl)
在插入操作中,由於插入的新節點,有可能使原本的二叉樹產生了失衡,故應該進行相應的旋轉操作。故,這樣插入操作能穩定在平均O(logn) 的時間復雜度內。
4.刪除操作(deleteAvl)
再刪除操作中,由於刪除了節點,也有可能是原本平衡的二叉樹產生失衡,故也應該進行相應的旋轉操作。故,這樣刪除操作能穩定在O(logn) 的時間復雜度內。
六、代碼實現(C/C++)
1.對於節點數據類型的處理:
1 #define Elem int //這樣Elem就是節點中數據的類型。
2.節點結構體的二叉鏈表
1 typedef struct LNode 2 { 3 Elem data; //節點數據域 4 int height; //節點為根的樹的高度 5 struct LNode *left,*right; //左子樹和右子樹 6 struct LNode() //構造函數 7 { 8 height=0; 9 left=right=NULL; 10 } 11 }LNode,*avlTree; 12 //這樣定義LNode是節點的類型,avlTree則是節點的指針類型。
3.右旋轉子操作,返回旋轉之后的根節點指針
1 avlTree _rightRotate(avlTree &r) 2 { 3 int lh=0,rh=0; 4 avlTree t=r->left; 5 r->left=t->right; 6 if(r->left) lh=r->left->height; 7 if(r->right) rh=r->right->height; 8 r->height=max(lh,rh)+1; 9 t->right=r; 10 if(t->left==NULL) t->height=1; 11 else t->height=max(t->left->height,r->height)+1; 12 return t; 13 }
4.左旋轉子操作,返回旋轉之后的根節點指針
1 avlTree _leftRotate(avlTree &r) 2 { 3 int lh=0,rh=0; 4 avlTree t=r->right; 5 r->right=t->left; 6 if(r->left) lh=r->left->height; 7 if(r->right) rh=r->right->height; 8 r->height=max(lh,rh)+1; 9 t->left=r; 10 if(t->right==NULL) t->height=1; 11 t->height=max(t->height,r->height)+1; 12 return t; 13 }
5.旋轉主算法(發生失衡,按照規則進行旋轉操作)
1 void rotateAvl(avlTree &root) 2 { 3 int lh=0,rh=0; 4 if(root->left) lh=root->left->height; 5 if(root->right) rh=root->right->height; 6 root->height=max(lh,rh)+1; 7 if(abs(lh-rh)>1) 8 { 9 avlTree tp; 10 int lp=0,rp=0; 11 if(lh>rh) tp=root->left; 12 else tp=root->right; 13 if(tp->left!=NULL) lp=tp->left->height; 14 if(tp->right!=NULL) rp=tp->right->height; 15 if(lh>rh&&lp<rp) root->left=_leftRotate(tp); 16 if(lh<rh&&lp>rp) root->right=_rightRotate(tp); 17 if(lh>rh) root=_rightRotate(root); 18 else root=_leftRotate(root); 19 } 20 }
6.插入操作,向二叉樹r插入d。這里用sign標記表示是否進行剃重,如果sign為true則剃重,sign為false則表示可重復。
1 void insertAvl(avlTree &r,Elem d,bool sign) 2 { 3 //遞歸出口 4 if(r==NULL) { 5 r=new LNode(); 6 r->data=d; 7 r->height++; 8 return; 9 } 10 if(d<=r->data) 11 { 12 if(d==r->data&&sign) return; 13 insertAvl(r->left,d,sign); 14 } 15 else 16 { 17 insertAvl(r->right,d,sign); 18 } 19 rotateAvl(r); //檢驗失衡並進行旋轉 20 }
7. 根據data數組建樹。這里用sign標記表示是否進行剃重,如果sign為true則剃重,sign為false則表示可重復。
1 void createAvl(avlTree &root,Elem data[],int n,bool sign) 2 { 3 int i; 4 root=NULL; 5 for(i=0;i<n;i++) 6 { 7 insertAvl(root,data[i],sign); 8 } 9 }
8.查詢root里面的數據d所在節點,如果查詢成功,則返回該節點。若d數據不存在,則查詢失敗,返回NULL。
1 avlTree searchAvl(avlTree root,Elem d) 2 { 3 if(root!=NULL) 4 { 5 if(d==root->data) return root; 6 else if(d<root->data) return searchAvl(root->left,d); 7 else searchAvl(root->right,d); 8 } 9 return NULL; 10 }
9.在刪除中尋找實際需要刪除的點,返回實際點。
1 avlTree _delete(avlTree &root) 2 { 3 avlTree ret=root; 4 if(root->left) ret=_delete(root->left); 5 else if(root->right) 6 { 7 ret=root->right; 8 int t=root->data; 9 root->data=root->right->data; 10 ret->data=t; 11 root->height=1; 12 root->right=NULL; 13 return ret; 14 } 15 else 16 { 17 root=NULL; 18 return ret; 19 } 20 rotateAvl(root); //檢驗失衡並進行旋轉 21 return ret; 22 }
10.刪除主操作算法,刪除root上的data數據所在節點
1 void deleteAvl(avlTree &root,Elem data) 2 { 3 avlTree ret; 4 if(!root) return; 5 if(root->data!=data) //未找到該節點,首先尋找該節點 6 { 7 if(data<root->data) ret=root->left; 8 else ret=root->right; 9 deleteAvl(ret,data); //遞歸尋找 10 } 11 else //找到該節點 12 { 13 if(!root->right) //右子樹為空 14 { 15 root=root->left; 16 } 17 else //右子樹不為空 18 { 19 avlTree t=_delete(root->right); //尋找實際刪除的節點 20 root->data=t->data; 21 free(t); 22 } 23 } 24 rotateAvl(root); //檢驗失衡並進行旋轉 25 }
於是我又找到了一份不錯的模版總結,僅供參考!
Treap樹
核心是 利用隨機數的二叉排序樹的各種操作復雜度平均為O(lgn)
Treap模板:
1 #include <cstdio> 2 #include <cstring> 3 #include <ctime> 4 #include <iostream> 5 #include <algorithm> 6 #include <cstdlib> 7 #include <cmath> 8 #include <utility> 9 #include <vector> 10 #include <queue> 11 #include <map> 12 #include <set> 13 #define max(x,y) ((x)>(y)?(x):(y)) 14 #define min(x,y) ((x)>(y)?(y):(x)) 15 #define INF 0x3f3f3f3f 16 #define MAXN 100005 17 18 using namespace std; 19 20 int cnt=1,rt=0; //節點編號從1開始 21 22 struct Tree 23 { 24 int key, size, pri, son[2]; //保證父親的pri大於兒子的pri 25 void set(int x, int y, int z) 26 { 27 key=x; 28 pri=y; 29 size=z; 30 son[0]=son[1]=0; 31 } 32 }T[MAXN]; 33 34 void rotate(int p, int &x) 35 { 36 int y=T[x].son[!p]; 37 T[x].size=T[x].size-T[y].size+T[T[y].son[p]].size; 38 T[x].son[!p]=T[y].son[p]; 39 T[y].size=T[y].size-T[T[y].son[p]].size+T[x].size; 40 T[y].son[p]=x; 41 x=y; 42 } 43 44 void ins(int key, int &x) 45 { 46 if(x == 0) 47 T[x = cnt++].set(key, rand(), 1); 48 else 49 { 50 T[x].size++; 51 int p=key < T[x].key; 52 ins(key, T[x].son[!p]); 53 if(T[x].pri < T[T[x].son[!p]].pri) 54 rotate(p, x); 55 } 56 } 57 58 void del(int key, int &x) //刪除值為key的節點 59 { 60 if(T[x].key == key) 61 { 62 if(T[x].son[0] && T[x].son[1]) 63 { 64 int p=T[T[x].son[0]].pri > T[T[x].son[1]].pri; 65 rotate(p, x); 66 del(key, T[x].son[p]); 67 } 68 else 69 { 70 if(!T[x].son[0]) 71 x=T[x].son[1]; 72 else 73 x=T[x].son[0]; 74 } 75 } 76 else 77 { 78 T[x].size--; 79 int p=T[x].key > key; 80 del(key, T[x].son[!p]); 81 } 82 } 83 84 int find(int p, int &x) //找出第p小的節點的編號 85 { 86 if(p == T[T[x].son[0]].size+1) 87 return x; 88 if(p > T[T[x].son[0]].size+1) 89 find(p-T[T[x].son[0]].size-1, T[x].son[1]); 90 else 91 find(p, T[x].son[0]); 92 } 93 94 int find_NoLarger(int key, int &x) //找出值小於等於key的節點個數 95 { 96 if(x == 0) 97 return 0; 98 if(T[x].key <= key) 99 return T[T[x].son[0]].size+1+find_NoLarger(key, T[x].son[1]); 100 else 101 return find_NoLarger(key, T[x].son[0]); 102 }
View Code
相關題目:POJ 3481 1442 2352
Splay Tree(伸展樹)
核心就是 過程Splay(x, y),即將x節點轉移到y節點的子節點上面(其中y是x的祖先)。
利用其中雙旋的優勢能夠保證查詢復雜度均攤為O(lgn)
一開始理解有些困難,其實實際上不做深入的理解就是,雙旋的過程就是一個建立相對平衡的二叉樹的一個過程。
》對於二叉樹,最極端的情況就是線性插入,使得整棵二叉樹退化為一條鏈。比如你查詢鏈的最后一個節點,之后再次查詢第一個節點。
1)若只是單旋通過Splay(x, 0)將最后一個節點移動到根節點,需要O(n)復雜度,而查詢第一個節點時又需要O(n)復雜度,來來往往就退化成一條鏈了。
2)若是雙旋Splay(x, 0)將最后一個節點移動到根節點上時,移動過程中建立起了相對平衡的二叉樹,需要O(n),也就是查詢第一個節點時,大概是需要O(lgn)復雜度。這就降低了復雜度。可以證明,總的每個操作的均攤復雜度是O(lgn)。
具體證明可以參見 楊思雨《伸展樹的基本操作與應用》
I 用於維護單調隊列 :(以key為維護對象保證單調)
常用版:(支持相同值)
1 Struct Tree{ 2 3 int key, size, fa, son[2]; 4 5 } 6 7 void PushUp(int x); 8 9 void Rotate(int x, int p); //0左旋 1右旋 10 11 void Splay(int x, int To) //將x節點插入到To的子節點中 12 13 int find(int key) //返回值為key的節點 若無返回0 若有將其轉移到根處 14 15 int prev() //返回比根值小的最大值 若無返回0 若有將其轉移到根處 16 17 int succ() //返回比根值大的最小值 若無返回0 若有將其轉移到根處 18 19 void Insert(int key) //插入key 並且將該節點轉移到根處 20 21 void Delete(int key) //刪除值為key的節點 若有重點只刪其中一個 x的前驅移動到根處 22 23 int GetPth(int p) //獲得第p小的節點 並將其轉移到根處 24 25 int GetRank(int key) //獲得值<=key的節點個數 並將其轉移到根處 若<key只需將<=換為<
模板:
1 int cnt=1, rt=0; 2 3 struct Tree 4 { 5 int key, size, fa, son[2]; 6 void set(int _key, int _size, int _fa) 7 { 8 key=_key; 9 size=_size; 10 fa=_fa; 11 son[0]=son[1]=0; 12 } 13 }T[MAXN]; 14 15 inline void PushUp(int x) 16 { 17 T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+1; 18 } 19 20 inline void Rotate(int x, int p) //0左旋 1右旋 21 { 22 int y=T[x].fa; 23 T[y].son[!p]=T[x].son[p]; 24 T[T[x].son[p]].fa=y; 25 T[x].fa=T[y].fa; 26 if(T[x].fa) 27 T[T[x].fa].son[T[T[x].fa].son[1] == y]=x; 28 T[x].son[p]=y; 29 T[y].fa=x; 30 PushUp(y); 31 PushUp(x); 32 } 33 34 void Splay(int x, int To) //將x節點插入到To的子節點中 35 { 36 while(T[x].fa != To) 37 { 38 if(T[T[x].fa].fa == To) 39 Rotate(x, T[T[x].fa].son[0] == x); 40 else 41 { 42 int y=T[x].fa, z=T[y].fa; 43 int p=(T[z].son[0] == y); 44 if(T[y].son[p] == x) 45 Rotate(x, !p), Rotate(x, p); //之字旋 46 else 47 Rotate(y, p), Rotate(x, p); //一字旋 48 } 49 } 50 if(To == 0) rt=x; 51 } 52 53 int find(int key) //返回值為key的節點 若無返回0 若有將其轉移到根處 54 { 55 int x=rt; 56 while(x && T[x].key != key) 57 x=T[x].son[key > T[x].key]; 58 if(x) Splay(x, 0); 59 return x; 60 } 61 62 int prev() //返回比根值小的最大值 若無返回0 若有將其轉移到根處 63 { 64 int x=T[rt].son[0]; 65 if(!x) return 0; 66 while(T[x].son[1]) 67 x=T[x].son[1]; 68 Splay(x, 0); 69 return x; 70 } 71 72 int succ() //返回比根值大的最小值 若無返回0 若有將其轉移到根處 73 { 74 int x=T[rt].son[1]; 75 if(!x) return 0; 76 while(T[x].son[0]) 77 x=T[x].son[0]; 78 Splay(x, 0); 79 return x; 80 } 81 82 void Insert(int key) //插入key 並且將該節點轉移到根處 83 { 84 if(!rt) 85 T[rt = cnt++].set(key, 1, 0); 86 else 87 { 88 int x=rt, y=0; 89 while(x) 90 { 91 y=x; 92 x=T[x].son[key > T[x].key]; 93 } 94 T[x = cnt++].set(key, 1, y); 95 T[y].son[key > T[y].key]=x; 96 Splay(x, 0); 97 } 98 } 99 100 void Delete(int key) //刪除值為key的節點 若有重點只刪其中一個 x的前驅移動到根處 101 { 102 int x=find(key); 103 if(!x) return; 104 int y=T[x].son[0]; 105 while(T[y].son[1]) 106 y=T[y].son[1]; 107 int z=T[x].son[1]; 108 while(T[z].son[0]) 109 z=T[z].son[0]; 110 if(!y && !z) 111 { 112 rt=0; 113 return; 114 } 115 if(!y) 116 { 117 Splay(z, 0); 118 T[z].son[0]=0; 119 PushUp(z); 120 return; 121 } 122 if(!z) 123 { 124 Splay(y, 0); 125 T[y].son[1]=0; 126 PushUp(y); 127 return; 128 } 129 Splay(y, 0); 130 Splay(z, y); 131 T[z].son[0]=0; 132 PushUp(z); 133 PushUp(y); 134 } 135 136 int GetPth(int p) //獲得第p小的節點 並將其轉移到根處 137 { 138 if(!rt) return 0; 139 int x=rt, ret=0; 140 while(x) 141 { 142 if(p == T[T[x].son[0]].size+1) 143 break; 144 if(p>T[T[x].son[0]].size+1) 145 { 146 p-=T[T[x].son[0]].size+1; 147 x=T[x].son[1]; 148 } 149 else 150 x=T[x].son[0]; 151 } 152 Splay(x, 0); 153 return x; 154 } 155 156 int GetRank(int key) //獲得值<=key的節點個數 並將其轉移到根處 若<key只需將<=換為< 157 { 158 if(!rt) return 0; 159 int x=rt, ret=0, y; 160 while(x) 161 { 162 y=x; 163 if(T[x].key <= key) 164 { 165 ret+=T[T[x].son[0]].size+1; 166 x=T[x].son[1]; 167 } 168 else 169 x=T[x].son[0]; 170 } 171 Splay(y, 0); 172 return ret; 173 }
View Code
完全版:(支持相同值,支持區間刪除,支持懶惰標記)
1 Struct Tree{ 2 3 int key, num, size, fa, son[2]; 4 5 } 6 7 void PushUp(int x); 8 9 void PushDown(int x); 10 11 int Newnode(int key, int fa); //新建一個節點並返回 12 13 void Rotate(int x, int p); //0左旋 1右旋 14 15 void Splay(int x, int To); //將x節點移動到To的子節點中 16 17 int GetPth(int p, int To); //返回第p小的節點 並移動到To的子節點中 18 19 int Find(int key); //返回值為key的節點 若無返回0 若有將其轉移到根處 20 21 int Prev(); //返回根節點的前驅 22 23 int Succ(); //返回根結點的后繼 24 25 void Insert(int key); //插入key值 26 27 void Delete(int key); //刪除值為key的節點 28 29 int GetRank(int key); //獲得值<=key的節點個數 30 31 void Delete(int l, int r); //刪除值在[l, r]中的節點
模板:
1 int cnt, rt; 2 int Add[MAXN]; 3 4 struct Tree{ 5 int key, num, size, fa, son[2]; 6 }T[MAXN]; 7 8 inline void PushUp(int x) 9 { 10 T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+T[x].num; 11 } 12 13 inline void PushDown(int x) 14 { 15 if(Add[x]) 16 { 17 if(T[x].son[0]) 18 { 19 T[T[x].son[0]].key+=Add[x]; 20 Add[T[x].son[0]]+=Add[x]; 21 } 22 if(T[x].son[1]) 23 { 24 T[T[x].son[1]].key+=Add[x]; 25 Add[T[x].son[1]]+=Add[x]; 26 } 27 Add[x]=0; 28 } 29 } 30 31 inline int Newnode(int key, int fa) //新建一個節點並返回 32 { 33 ++cnt; 34 T[cnt].key=key; 35 T[cnt].num=T[cnt].size=1; 36 T[cnt].fa=fa; 37 T[cnt].son[0]=T[cnt].son[1]=0; 38 return cnt; 39 } 40 41 inline void Rotate(int x, int p) //0左旋 1右旋 42 { 43 int y=T[x].fa; 44 PushDown(y); 45 PushDown(x); 46 T[y].son[!p]=T[x].son[p]; 47 T[T[x].son[p]].fa=y; 48 T[x].fa=T[y].fa; 49 if(T[x].fa) 50 T[T[x].fa].son[T[T[x].fa].son[1] == y]=x; 51 T[x].son[p]=y; 52 T[y].fa=x; 53 PushUp(y); 54 PushUp(x); 55 } 56 57 void Splay(int x, int To) //將x節點移動到To的子節點中 58 { 59 while(T[x].fa != To) 60 { 61 if(T[T[x].fa].fa == To) 62 Rotate(x, T[T[x].fa].son[0] == x); 63 else 64 { 65 int y=T[x].fa, z=T[y].fa; 66 int p=(T[z].son[0] == y); 67 if(T[y].son[p] == x) 68 Rotate(x, !p), Rotate(x, p); //之字旋 69 else 70 Rotate(y, p), Rotate(x, p); //一字旋 71 } 72 } 73 if(To == 0) rt=x; 74 } 75 76 int GetPth(int p, int To) //返回第p小的節點 並移動到To的子節點中 77 { 78 if(!rt || p > T[rt].size) return 0; 79 int x=rt; 80 while(x) 81 { 82 PushDown(x); 83 if(p >= T[T[x].son[0]].size+1 && p <= T[T[x].son[0]].size+T[x].num) 84 break; 85 if(p > T[T[x].son[0]].size+T[x].num) 86 { 87 p-=T[T[x].son[0]].size+T[x].num; 88 x=T[x].son[1]; 89 } 90 else 91 x=T[x].son[0]; 92 } 93 Splay(x, 0); 94 return x; 95 } 96 97 int Find(int key) //返回值為key的節點 若無返回0 若有將其轉移到根處 98 { 99 if(!rt) return 0; 100 int x=rt; 101 while(x) 102 { 103 PushDown(x); 104 if(T[x].key == key) break; 105 x=T[x].son[key > T[x].key]; 106 } 107 if(x) Splay(x, 0); 108 return x; 109 } 110 111 int Prev() //返回根節點的前驅 非重點 112 { 113 if(!rt || !T[rt].son[0]) return 0; 114 int x=T[rt].son[0]; 115 while(T[x].son[1]) 116 { 117 PushDown(x); 118 x=T[x].son[1]; 119 } 120 Splay(x, 0); 121 return x; 122 } 123 124 int Succ() //返回根結點的后繼 非重點 125 { 126 if(!rt || !T[rt].son[1]) return 0; 127 int x=T[rt].son[1]; 128 while(T[x].son[0]) 129 { 130 PushDown(x); 131 x=T[x].son[0]; 132 } 133 Splay(x, 0); 134 return x; 135 } 136 137 void Insert(int key) //插入key值 138 { 139 if(!rt) 140 rt=Newnode(key, 0); 141 else 142 { 143 int x=rt, y=0; 144 while(x) 145 { 146 PushDown(x); 147 y=x; 148 if(T[x].key == key) 149 { 150 T[x].num++; 151 T[x].size++; 152 break; 153 } 154 T[x].size++; 155 x=T[x].son[key > T[x].key]; 156 } 157 if(!x) 158 x=T[y].son[key > T[y].key]=Newnode(key, y); 159 Splay(x, 0); 160 } 161 } 162 163 void Delete(int key) //刪除值為key的節點1個 164 { 165 int x=Find(key); 166 if(!x) return; 167 if(T[x].num>1) 168 { 169 T[x].num--; 170 PushUp(x); 171 return; 172 } 173 int y=T[x].son[0]; 174 while(T[y].son[1]) 175 y=T[y].son[1]; 176 int z=T[x].son[1]; 177 while(T[z].son[0]) 178 z=T[z].son[0]; 179 if(!y && !z) 180 { 181 rt=0; 182 return; 183 } 184 if(!y) 185 { 186 Splay(z, 0); 187 T[z].son[0]=0; 188 PushUp(z); 189 return; 190 } 191 if(!z) 192 { 193 Splay(y, 0); 194 T[y].son[1]=0; 195 PushUp(y); 196 return; 197 } 198 Splay(y, 0); 199 Splay(z, y); 200 T[z].son[0]=0; 201 PushUp(z); 202 PushUp(y); 203 } 204 205 int GetRank(int key) //獲得值<=key的節點個數 206 { 207 if(!Find(key)) 208 { 209 Insert(key); 210 int tmp=T[T[rt].son[0]].size; 211 Delete(key); 212 return tmp; 213 } 214 else 215 return T[T[rt].son[0]].size+T[rt].num; 216 } 217 218 void Delete(int l, int r) //刪除值在[l, r]中的所有節點 l!=r 219 { 220 if(!Find(l)) Insert(l); 221 int p=Prev(); 222 if(!Find(r)) Insert(r); 223 int q=Succ(); 224 if(!p && !q) 225 { 226 rt=0; 227 return; 228 } 229 if(!p) 230 { 231 T[rt].son[0]=0; 232 PushUp(rt); 233 return; 234 } 235 if(!q) 236 { 237 Splay(p, 0); 238 T[rt].son[1]=0; 239 PushUp(rt); 240 return; 241 } 242 Splay(p, q); 243 T[p].son[1]=0; 244 PushUp(p); 245 PushUp(q); 246 }
View Code
相關題目:HNOI 2002 POJ3481 POJ2352 POJ1442
NOI2004 郁悶的出納員
II 用於維護序列: (以序列下標為對象維護,相當於對區間操作)(能夠完成線段樹的操作及其不能完成的操作)
1 Struct Tree{ 2 3 int key, sum, size, fa, son[2]; 4 5 } 6 7 支持操作: 8 9 void PushUp(int x); 10 11 void PushDown(int x); 12 13 int MakeTree(int l, int r, int a[]); //新建一個子樹返回根節點 14 15 void Rotate(int x, int p); //0左旋 1右旋 16 17 void Splay(int x, int To); //將x節點移動到To的子節點中 18 19 int Select(int p, int To); //將第p個數移動到To的子節點中 並返回該節點 20 21 int Find(int key); //返回值為key的節點 若無返回0 若有將其轉移到根處 22 23 int Prev(); //返回根節點的前驅 24 25 int Succ(); //返回根結點的后繼 26 27 void Insert(int p, int l, int r, int a[]) //將a[l .. r]的數插入到下標為p后面 28 29 void Delete(int l, int r); //刪除區間[l, r]中的節點 30 31 int Query(int l, int r); //返回[l, r]的和 32 33 待補充。。 34 35 Size Balance Tree 36 37 和上述兩種二叉樹比起來,SBT可能是最像真正平衡二叉樹吧。 38 39 SBT能夠保證樹的高度在lgn,這樣對於插入,刪除操作都能夠准確保證時間復雜度在O(lgn) 40 41 Maintain操作事實上理解起來也是挺簡單的,至於證明參見CQF神牛的 《SBT》
模版:
1 int cnt, rt; 2 3 struct Tree 4 { 5 int key, size, son[2]; 6 }T[MAXN]; 7 8 inline void PushUp(int x) 9 { 10 T[x].size=T[T[x].son[0]].size+T[T[x].son[1]].size+1; 11 } 12 13 inline int Newnode(int key) 14 { 15 ++cnt; 16 T[cnt].key=key; 17 T[cnt].size=1; 18 T[cnt].son[0]=T[cnt].son[1]=0; 19 return cnt; 20 } 21 22 void Rotate(int p, int &x) 23 { 24 int y=T[x].son[!p]; 25 T[x].son[!p]=T[y].son[p]; 26 T[y].son[p]=x; 27 PushUp(x); 28 PushUp(y); 29 x=y; 30 } 31 32 void Maintain(int &x, int p) //維護SBT的!p子樹 33 { 34 if(T[T[T[x].son[p]].son[p]].size > T[T[x].son[!p]].size) 35 Rotate(!p, x); 36 else if(T[T[T[x].son[p]].son[!p]].size > T[T[x].son[!p]].size) 37 Rotate(p, T[x].son[p]), Rotate(!p, x); 38 else return; 39 Maintain(T[x].son[0], 0); 40 Maintain(T[x].son[1], 1); 41 Maintain(x, 0); 42 Maintain(x, 1); 43 } 44 45 inline int Prev() //返回比根值小的最大值 若無返回0 46 { 47 int x=T[rt].son[0]; 48 if(!x) return 0; 49 while(T[x].son[1]) 50 x=T[x].son[1]; 51 return x; 52 } 53 54 inline int Succ() //返回比根值大的最小值 若無返回0 55 { 56 int x=T[rt].son[1]; 57 if(!x) return 0; 58 while(T[x].son[0]) 59 x=T[x].son[0]; 60 return x; 61 } 62 63 void Insert(int key, int &x) 64 { 65 if(!x) x=Newnode(key); 66 else 67 { 68 T[x].size++; 69 Insert(key, T[x].son[key > T[x].key]); 70 Maintain(x, key > T[x].key); 71 } 72 } 73 74 bool Delete(int key, int &x) //刪除值為key的節點 key可以不存在 75 { 76 if(!x) return 0; 77 if(T[x].key == key) 78 { 79 if(!T[x].son[0]) 80 { 81 x=T[x].son[1]; 82 return 1; 83 } 84 if(!T[x].son[1]) 85 { 86 x=T[x].son[0]; 87 return 1; 88 } 89 int y=Prev(); 90 T[x].size--; 91 return Delete(T[x].key, T[x].son[0]); 92 } 93 else 94 if(Delete(key, T[x].son[key > T[x].key])) 95 { 96 T[x].size--; 97 return 1; 98 } 99 } 100 101 int GetPth(int p, int &x) //返回第p小的節點 102 { 103 if(!x) return 0; 104 if(p == T[T[x].son[0]].size+1) 105 return x; 106 if(p > T[T[x].son[0]].size+1) 107 return GetPth(p-T[T[x].son[0]].size-1, T[x].son[1]); 108 else 109 return GetPth(p, T[x].son[0]); 110 } 111 112 int GetRank(int key, int &x) //找出值<=key的節點個數 113 { 114 if(!x) return 0; 115 if(T[x].key <= key) 116 return T[T[x].son[0]].size+1+GetRank(key, T[x].son[1]); 117 else 118 return GetRank(key, T[x].son[0]); 119 }
View Code
相關題目:POJ 3481
上述題均為用於測試平衡樹基本操作的題目。
提高題:
在文章的最后貼上一個二叉樹專題訓練https://vjudge.net/contest/84416;jsessionid=E73DCD38F70FF141A029A2DB5958B2F1
喜歡的點個贊並訂閱我們,我們將會提供最優質的文章供大家學習參考,歡迎大家一起學習QAQ