1. 程式人生 > >平衡二叉樹總結三:treap樹(樹堆)

平衡二叉樹總結三:treap樹(樹堆)

  類似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);
}