旋转式Treap入门

Treap 是一种常见的平衡树。二叉搜索树(BST)的缺陷是容易退化,而研究表明,随机构造的 BST 是趋向于平衡的,Treap 就是一种更改了结点排序方式的 BST。Treap 为每个结点引入了一个额外的随机权值 priority,一个结点的 priority 小于其子树中所有结点的 priority(堆性质),这也是 Treap 这个名字的由来(Tree + Heap)。本文介绍旋转式 Treap,还有一种无旋 Treap 以后再做介绍。

维持平衡

旋转式 Treap 自然通过旋转来维持平衡。不过,Treap 的旋转只有两种:zig 和 zag。

     A                  B        
    / \    --zig->     / \    
   B   C              D   A
  / \      <-zag--       / \  
 D   E                  E   C

当树的形态发生变化,堆性质无法满足时,就需要进行旋转。

插入

先给结点随机分配一个 priority,然后根据 BST 的性质将其插入到一个叶子上,再根据堆性质将该结点向上旋转。

删除

找到需要删除的结点,向 priority 更大的儿子方向旋转直到成为叶子,最后删除。

查询

和普通的 BST 类似。

实现

namespace treap {
std::mt19937 rnd(time(nullptr));
struct node {
    int size, key, priority, cnt, ch[2];
};
node t[maxn];
int tot = 0, root = 0;
void push_up(int p) {
    t[p].size = t[t[p].ch[0]].size + t[t[p].ch[1]].size + t[p].cnt;
}
/********************************
 *     A                  B     *
 *    / \    --zig->     / \    *
 *   B   C              D   A   *
 *  / \      <-zag--       / \  *
 * D   E                  E   C *
 ********************************/
void rotate(int &p, int op) {
    // op == 0 ? zig : zag
    int s = t[p].ch[op ^ 1];
    t[p].ch[op ^ 1] = t[s].ch[op];    // reconnect node E
    t[s].ch[op] = p, p = s;           // change father
    push_up(t[p].ch[op]), push_up(p); // update
}
void insert(int v, int &p = root) {
    if (!p) {
        p = ++tot;
        t[p].size = t[p].cnt = 1;
        t[p].key = v, t[p].priority = rnd();
        t[p].ch[0] = t[p].ch[1] = 0;
        return;
    }
    t[p].size++;
    if (t[p].key == v)
        t[p].cnt++;
    else if (t[p].key < v) {
        insert(v, t[p].ch[1]);
        if (t[t[p].ch[1]].priority < t[p].priority)
            rotate(p, 0); // zag
    } else {
        insert(v, t[p].ch[0]);
        if (t[t[p].ch[0]].priority < t[p].priority)
            rotate(p, 1); // zig
    }
}
void erase(int v, int &p = root) {
    if (!p)
        return;
    if (t[p].key == v) {
        if (t[p].cnt > 1) {
            t[p].cnt--;
            push_up(p);
            return;
        }
        if (!t[p].ch[0] || !t[p].ch[1]) {
            p = t[p].ch[0] + t[p].ch[1];
        } else if (t[t[p].ch[0]].priority < t[t[p].ch[0]].priority) {
            rotate(p, 1);
            erase(v, p);
        } else {
            rotate(p, 0);
            erase(v, p);
        }
    } else if (t[p].key < v) {
        erase(v, t[p].ch[1]);
        push_up(p);
    } else {
        erase(v, t[p].ch[0]);
        push_up(p);
    }
}
int rank(int v, int p = root) {
    if (!p)
        return 1;
    if (t[p].key == v)
        return t[t[p].ch[0]].size + 1;
    else if (t[p].key < v) {
        return t[t[p].ch[0]].size + t[p].cnt + rank(v, t[p].ch[1]);
    } else {
        return rank(v, t[p].ch[0]);
    }
}
int kth(int v, int p = root) {
    if (!p)
        return 0;
    if (v <= t[t[p].ch[0]].size) {
        return kth(v, t[p].ch[0]);
    } else if (v > t[t[p].ch[0]].size + t[p].cnt) {
        return kth(v - t[t[p].ch[0]].size - t[p].cnt, t[p].ch[1]);
    } else {
        return t[p].key;
    }
}
int pre(int v, int p = root) {
    int ret = inf;
    while (p) {
        if (t[p].key < v)
            ret = t[p].key, p = t[p].ch[1];
        else
            p = t[p].ch[0];
    }
    return ret;
}
int suf(int v, int p = root) {
    int ret = -inf;
    while (p) {
        if (t[p].key > v)
            ret = t[p].key, p = t[p].ch[0];
        else
            p = t[p].ch[1];
    }
    return ret;
}
}; // namespace treap

性质

旋转式 Treap 的期望高度是 \(\Theta(\log n)\),并且在 insert 操作中旋转的期望次数小于 \(2\)。和其他常见平衡树相比,旋转式 Treap 除了常数较小外并没有什么优势。

posted @ 2021-10-04 16:05  Theophania  阅读(68)  评论(0)    收藏  举报