平衡二叉樹總結三:treap樹(樹堆)
阿新 • • 發佈:2019-01-30
類似avl樹的還有紅黑樹和伸展樹,然而程式設計確實很複雜,我先總結treap樹吧,比賽啥的也能用得上。從樹堆這個名字不難看出treap這種資料結構應該同時具有二叉搜尋樹與二叉堆的某些性質,實際上樹堆首先是一顆二叉搜尋樹,也就是說它滿足 left < root < right;其次它還有一個優先順序域(插入時隨機給予的),該優先順序域需滿足left < root && right < root,也就是所謂的堆序性,由於優先順序是隨機給予的,這樣得到的搜尋樹平均深度為log(n)(演算法導論沒看懂,證明不來QAQ),難點在於在插入與刪除過程中維護這兩個性質。
0.treap樹一般結構
struct treap{
treap* left;
treap* right;
int val,priority;//值域,優先順序域。
treap(int v,int p):val(v),priority(p),left(NULL),right(NULL){}
};
typedef treap* tree;
1.插入
插入過程中需要用到AVL旋轉操作,我們知道旋轉不改變二叉搜尋樹的性質(只改變深度),左單旋與右單旋可以分別把左右兒子提到根節點,我們可以通過該方法來維護堆序性。插入時先按照二叉搜尋插入規則插入,然後在回溯過程中,檢查子樹優先順序是否比根節點小,如果比根節點小,就把它旋轉到根節點,這樣一步步回溯到最後就保證了treap的堆序性。
//由於沒有高度,所以旋轉時不需要更新高度。 tree LL(tree t){ tree t1 = t->left; t->left = t1->right; t1->right = t; return t1; } tree RR(tree t){ tree t1 = t->right; t->right = t1->left; t1->left = t; return t1; } tree insert(tree t,int val){ if(t==NULL){ t = new treap(val,rand());//隨機生成優先順序。 return t; } if(val > t->val){ t->right = insert(t->right,val);//往右插 //插入結束檢查優先順序是否正確。 if(t->right->priority < t->priority){t = RR(t);} } else if(val < t->val){ t->left = insert(t->left,val); if(t->left->priority < t->priority){t = LL(t);} } return t; }
2.刪除
相對avl而言,treap樹的刪除程式碼簡單多了,沒有太多複雜的情形,找到待刪除點之後:
(0)該點沒有兒子時,直接刪掉它。
(1)如果該點只有一個兒子,直接用該兒子代替它即可;
(2)兩個兒子時,把優先順序較小的兒子旋轉到該點,然後繼續遞迴刪除該點所在的子樹(直到滿足情況一)。
tree remove(tree t,int val){
if(t==NULL)return t;
if(val > t->val)t->right = remove(t->right,val);//走右邊
else if(val < t->val)t->left = remove(t->left,val);
else{
if(t->left && t->right){//有倆兒子
//左兒子優先順序小,把它旋轉到根
if(t->left->priority < t->right->priority){
t = LL(t);
t->right = remove(t->right,val);//遞迴刪除
}
else{
t = RR(t);
t->left = remove(t->left,val);
}
}
else{
//如果左子樹存在就直接返回它,否則返回右子樹。
tree v = t->left ? t->left : t->right;
delete t;
return v;
}
}
return t;
}
3.總結
總的來說treap樹的程式設計相對與avl樹而言是很簡單的,效能平均效果也不差,大部分情形都能hold住,不過似乎主要用在比賽上,工程裡應用多的還是紅黑樹這種絕對最壞效能達到log(n)的資料結構,不過作為隨機化方法的典型,學習一下還是很有必要的。
4.貼一下整體的程式碼吧
#include<iostream>
#include<stdlib.h>
#include<time.h>
#include<queue>
using namespace std;
struct treap{
treap* left;
treap* right;
int val,priority;
treap(int v,int p):val(v),priority(p),left(NULL),right(NULL){}
};
typedef treap* tree;
//由於沒有高度,所以旋轉時不需要更新高度。
tree LL(tree t){
tree t1 = t->left;
t->left = t1->right;
t1->right = t;
return t1;
}
tree RR(tree t){
tree t1 = t->right;
t->right = t1->left;
t1->left = t;
return t1;
}
tree insert(tree t,int val){
if(t==NULL){
t = new treap(val,rand());//隨機生成優先順序。
return t;
}
if(val > t->val){
t->right = insert(t->right,val);//往右插
//插入結束檢查優先順序是否正確。
if(t->right->priority < t->priority){t = RR(t);}
}
else if(val < t->val){
t->left = insert(t->left,val);
if(t->left->priority < t->priority){t = LL(t);}
}
return t;
}
tree remove(tree t,int val){
if(t==NULL)return t;
if(val > t->val)t->right = remove(t->right,val);//走右邊
else if(val < t->val)t->left = remove(t->left,val);
else{
if(t->left && t->right){//有倆兒子
//左兒子優先順序小,把它旋轉到根
if(t->left->priority < t->right->priority){
t = LL(t);
t->right = remove(t->right,val);//遞迴刪除
}
else{
t = RR(t);
t->left = remove(t->left,val);
}
}
else{
//如果左子樹存在就直接返回它,否則返回右子樹。
tree v = t->left ? t->left : t->right;
delete t;
return v;
}
}
return t;
}
void travel(tree t){
if(!t)return;
if(t->left)travel(t->left);
cout << t->val << " ";
if(t->right)travel(t->right);
}
void level(tree t){
if(!t)return;
tree now,last=t;
queue<tree> qu;
qu.push(t);
while(qu.size()){
now = qu.front();qu.pop();
if(now->left)qu.push(now->left);
if(now->right)qu.push(now->right);
cout << now->val << "(" << now->priority << ")" << " ";
if(now == last && qu.size()){last = qu.back();cout << endl;}
}
cout << endl;
}
int main(){
int a[10] = {1,8,3,0,9,5,6,2,4,7};
tree t = NULL;
int i;
srand(time(0));
for(i=0;i<10;i++){
level(t);cout << "************************\n";
t = insert(t,a[i]);
}
travel(t);cout << endl;
level(t);
t = remove(t,8);
travel(t);cout << endl;
level(t);
}