旋转式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 除了常数较小外并没有什么优势。

浙公网安备 33010602011771号