{Treap}

11831 蔡嘉晉

tr-eap?

tree+heap

樹堆(treap)

  • 兼具二元搜尋樹與堆的性質
  • 二元搜尋樹:
    • 左子樹所有節點大於父節點
    • 右子樹所有節點小於父節點
  • 堆:
    • 子節點值小於(大於)父節點值

 

 

 

 

 

why treap?

  • 一個平衡的二元搜尋樹深度為 \(log_2N\)
  • 那如果插入節點時不平衡呢

 

  • 例:插入順序為
    • 1  2  3  4  5

 

  • 深度及複雜度都會退化為線性!

1

2

3

4

5

how to treap?

  • 生成每個節點的時候隨機一個代表用在heap的值
  • 在每次更動treap的時候透過旋轉維護性質

 

 

 

 

節點

  • 就跟正常的二元樹一樣啊
struct Node {
  Node *ch[2];  // 兩個子節點的位址
  int val, rank;
  int rep_cnt;  // 該節點重複出現次數
  int siz;

  Node(int val) : val(val), rep_cnt(1), siz(1) {
    ch[0] = ch[1] = nullptr;
    prior = rand(); //random出一個priority供heap使用
  }

  void upd_siz() {
    // 旋轉或刪除等操作後重新計算size
    siz = rep_cnt;
    if (ch[0] != nullptr) siz += ch[0]->siz;
    if (ch[1] != nullptr) siz += ch[1]->siz;
  }
};

旋轉

  • 為什麼要旋轉?
    • 不然要怎麼維護?
    • 因為新插入的值priorty可能不符合heap的結構
    • 旋轉可以維持二元搜尋樹的特徵
    • 但是將部分節點向上調一層 部分下調以符合heap結構

 

 

 

 

旋轉

  • 左旋
    • 根節點的右子節點成為根節點
    • 新根節點因此有三個子節點
    • 將原先的右子樹接到原根節點的左子樹

2

3

1

5

4

1

2

3

4

5

2

3

1

4

5

右旋的話就相反

void rotate(Node *&cur, int dir) {//dir=0 or 1 0右旋 1左旋
  Node *tmp = cur->ch[dir]; 
  cur->ch[dir] = tmp->ch[!dir];    // 將 舊根節點 的 右子樹 變成 新根節點的左子樹
  tmp->ch[!dir] = cur;             // 將 新根節點 的 左子樹 變成 舊根節點及其子樹
  tmp->upd_siz(), cur->upd_siz();  // 更新大小
  cur = tmp;  // 最后把臨時儲存 新根節點 的節點變成根結點
}
# PRESENTING CODE

旋轉

treap插入&刪除

  • 就跟正常的二元搜尋樹類似
  • 但是要透過旋轉維護

 

 

 

void insert(Node *&cur, int val) {
  if (cur == nullptr) {// 這棵treap甚至是空的
    cur = new Node(val);
    return;
  } else if (val == cur->val) {// 如果有这个值相同的节点,就把重复数量加一
    cur->rep_cnt++;
    cur->siz++;
  } else if (val < cur->val) { // 維護二元搜尋樹性质,val 比當前節點小就插到左邊,反之亦然
    insert(cur->ch[0], val);
    if (cur->ch[0]->rank < cur->rank) {
      // 小根堆中,上面節點的priority一定更小
      // 因為新插的左子節點比父節點小,现在需要讓左子節點變成父節點
      rotate(cur, RT);  // 要把左子節點转上来,需要右旋
    }
    cur->upd_siz();  // 插入之后大小會變化,需要更新
  } else {
    _insert(cur->ch[1], val);
    if (cur->ch[1]->rank < cur->rank) {
      _rotate(cur, LF);
    }
    cur->upd_siz();
  }
}

插入

  • 首先遞迴找到欲刪除的節點並分項討論
    • 無子節點
      • 直接刪了
    • 無右/左節點
      • 刪了節點並將另外一個子節點接上
    • 都有
      • 將子節點中較小的那個旋轉上去
      • 旋轉完成後原来的根節點就在旋轉方向那邊,所以需要
      • 繼續把這個原来的根節點繼續遞迴下去
      • 如果要刪的這個節點是在整個樹的「上層的」
      • 那通過遞迴的旋轉操作,便可將他轉到沒有子樹了(或者只有一個),再刪掉它

刪除

void _del(Node *&cur, int val) {
  if (val > cur->val) {
    _del(cur->ch[1], val);
    // 值更大就在右子樹,反之亦然
    cur->upd_siz();
  } else if (val < cur->val) {
    _del(cur->ch[0], val);
    cur->upd_siz();
  } else {
    if (cur->rep_cnt > 1) {
      // 如果要刪除的節點是重複的,可以直接把重複值减小
      cur->rep_cnt--, cur->siz--;
      return;
    }
    int state = 0;
    state |= (cur->ch[0] != nullptr);
    state |= ((cur->ch[1] != nullptr) << 1);
    // 00都無,01有左無右,10,無左有右,11都有
    Node *tmp = cur;
    switch (state) {
      case 0:
        delete cur;
        cur = nullptr;
        // 没有任何子節點,就直接把這個節點刪了
        break;
      case 1:  // 有左無右
        cur = tmp->ch[0];
        // 把根變成左子節點,然后把原来的根節點刪除
        delete tmp;
        break;
      case 2:  // 有右無左
        cur = tmp->ch[1];
        delete tmp;
        break;
      case 3:
        rot_type dir = cur->ch[0]->rank < cur->ch[1]->rank ? 0 : 1;  // dir 是 rank 更小的那个儿子
        _rotate(cur, dir);
        _del(
            cur->ch[!dir],
            val);  // 旋轉完成後原来的根節點就在旋方向那邊,所以需要
                   // 繼續把這個原来的根節點繼續遞迴下去
                   // 如果要刪的這個節點是在整個樹的「上層的」
                   // 那通過遞迴的旋轉操作,便可將他轉到沒有子樹了(或者只有一個),再刪掉它。
        cur->upd_siz();
        // 刪除會造成大小改變
        break;
    }
  }
}
# PRESENTING CODE

刪除

treap操作

好像已經跟treap沒關係了 正常的二元搜尋樹都行

查詢排名

  • 因爲紀錄節點的時候順便紀錄了那棵子樹的大小
  • 所以可以直接取用
  • 如果欲查詢的數小於目前的數
    • 則向左子樹遞迴
    • 否則向右子樹遞迴並且加上小於等於目前的數的數量

 

 

 

 

 

int _query_rank(Node *cur, int val) {
  int less_siz = cur->ch[0] == nullptr ? 0 : cur->ch[0]->siz;
  if (val == cur->val)
    return less_siz + 1;
  else if (val < cur->val) {
    if (cur->ch[0] != nullptr)
      return _query_rank(cur->ch[0], val);
    else
      return 1;  // 如果左子樹是空的,代表比最小的節點還要小,那這個數字就是最小的
  } else {
    if (cur->ch[1] != nullptr)
      return less_siz + cur->rep_cnt + _query_rank(cur->ch[1], val);
    else
      return cur->siz + 1;
  }
}

以排名查詢值

  • 好像有個東西叫做nth_element
  • 看左子樹的大小有沒有大於要查詢的排名
    • 若大於則向左遞迴
    • 否則向右

 

 

 

 

 

int _query_val(Node *cur, int rank) {
  int less_siz = cur->ch[0] == nullptr ? 0 : cur->ch[0]->siz;
  // less siz 是左子樹的大小
  if (rank <= less_siz)
    return _query_val(cur->ch[0], rank);
  else if (rank <= less_siz + cur->rep_cnt)
    return cur->val;
  else
    return _query_val(cur->ch[1], rank - less_siz - cur->rep_cnt);
}

查詢第一個比指定值大/小的值

  • 比指定值大
    • 尋找該值右子樹中最左邊的節點
  • 比指定值小
    • 尋找該值左子樹中右左邊的節點
// author: (ttzytt)[ttzytt.com]
#include <bits/stdc++.h>
using namespace std;

struct Node {
  Node *ch[2];
  int val, rank;
  int rep_cnt;
  int siz;

  Node(int val) : val(val), rep_cnt(1), siz(1) {
    ch[0] = ch[1] = nullptr;
    rank = rand();
  }

  void upd_siz() {
    siz = rep_cnt;
    if (ch[0] != nullptr) siz += ch[0]->siz;
    if (ch[1] != nullptr) siz += ch[1]->siz;
  }
};

class Treap {
 private:
  Node *root;

  enum rot_type { LF = 1, RT = 0 };

  int q_prev_tmp = 0, q_nex_tmp = 0;

  void _rotate(Node *&cur, rot_type dir) {
    Node *tmp = cur->ch[dir];
    cur->ch[dir] = tmp->ch[!dir];
    tmp->ch[!dir] = cur;
    tmp->upd_siz(), cur->upd_siz();
    cur = tmp;
  }

  void _insert(Node *&cur, int val) {
    if (cur == nullptr) {
      cur = new Node(val);
      return;
    } else if (val == cur->val) {
      cur->rep_cnt++;
      cur->siz++;
    } else if (val < cur->val) {
      _insert(cur->ch[0], val);
      if (cur->ch[0]->rank < cur->rank) {
        _rotate(cur, RT);
      }
      cur->upd_siz();
    } else {
      _insert(cur->ch[1], val);
      if (cur->ch[1]->rank < cur->rank) {
        _rotate(cur, LF);
      }
      cur->upd_siz();
    }
  }

  void _del(Node *&cur, int val) {
    if (val > cur->val) {
      _del(cur->ch[1], val);
      cur->upd_siz();
    } else if (val < cur->val) {
      _del(cur->ch[0], val);
      cur->upd_siz();
    } else {
      if (cur->rep_cnt > 1) {
        cur->rep_cnt--, cur->siz--;
        return;
      }
      uint8_t state = 0;
      state |= (cur->ch[0] != nullptr);
      state |= ((cur->ch[1] != nullptr) << 1);
      Node *tmp = cur;
      switch (state) {
        case 0:
          delete cur;
          cur = nullptr;
          break;
        case 1:  // 有左无右
          cur = tmp->ch[0];
          delete tmp;
          break;
        case 2:  // 有右无左
          cur = tmp->ch[1];
          delete tmp;
          break;
        case 3:
          rot_type dir = cur->ch[0]->rank < cur->ch[1]->rank ? RT : LF;
          _rotate(cur, dir);
          _del(cur->ch[!dir], val);
          cur->upd_siz();
          break;
      }
    }
  }

  int _query_rank(Node *cur, int val) {
    int less_siz = cur->ch[0] == nullptr ? 0 : cur->ch[0]->siz;
    if (val == cur->val)
      return less_siz + 1;
    else if (val < cur->val) {
      if (cur->ch[0] != nullptr)
        return _query_rank(cur->ch[0], val);
      else
        return 1;
    } else {
      if (cur->ch[1] != nullptr)
        return less_siz + cur->rep_cnt + _query_rank(cur->ch[1], val);
      else
        return cur->siz + 1;
    }
  }

  int _query_val(Node *cur, int rank) {
    int less_siz = cur->ch[0] == nullptr ? 0 : cur->ch[0]->siz;
    if (rank <= less_siz)
      return _query_val(cur->ch[0], rank);
    else if (rank <= less_siz + cur->rep_cnt)
      return cur->val;
    else
      return _query_val(cur->ch[1], rank - less_siz - cur->rep_cnt);
  }

  int _query_prev(Node *cur, int val) {
    if (val <= cur->val) {
      if (cur->ch[0] != nullptr) return _query_prev(cur->ch[0], val);
    } else {
      q_prev_tmp = cur->val;
      if (cur->ch[1] != nullptr) _query_prev(cur->ch[1], val);
      return q_prev_tmp;
    }
    return -1145;
  }

  int _query_nex(Node *cur, int val) {
    if (val >= cur->val) {
      if (cur->ch[1] != nullptr) return _query_nex(cur->ch[1], val);
    } else {
      q_nex_tmp = cur->val;
      if (cur->ch[0] != nullptr) _query_nex(cur->ch[0], val);
      return q_nex_tmp;
    }
    return -1145;
  }

 public:
  void insert(int val) { _insert(root, val); }

  void del(int val) { _del(root, val); }

  int query_rank(int val) { return _query_rank(root, val); }

  int query_val(int rank) { return _query_val(root, rank); }

  int query_prev(int val) { return _query_prev(root, val); }

  int query_nex(int val) { return _query_nex(root, val); }
};

Treap tr;

int main() {
  srand(0);
  int t;
  scanf("%d", &t);
  while (t--) {
    int mode;
    int num;
    scanf("%d%d", &mode, &num);
    switch (mode) {
      case 1:
        tr.insert(num);
        break;
      case 2:
        tr.del(num);
        break;
      case 3:
        printf("%d\n", tr.query_rank(num));
        break;
      case 4:
        printf("%d\n", tr.query_val(num));
        break;
      case 5:
        printf("%d\n", tr.query_prev(num));
        break;
      case 6:
        printf("%d\n", tr.query_nex(num));
        break;
    }
  }
}
# PRESENTING CODE

完整的扣

感謝OI wiki不然我做不完

Made with Slides.com