您的位置:首页 > 其它

Treap 学习笔记

2017-11-16 15:31 295 查看

Treap

Treap,顾名思义就是 Tree + Heap。这么命名的原因就是它使用了二叉堆的性质来保持二叉树的平衡。

我们知道,一个二叉(小根)堆满足这样的性质:一个节点的两个儿子的值都小于节点本身的值。如果一个二叉查找树满足这样的性质,那么它就被称作 Treap。

Treap 中每个节点有 2 个值,其中一个满足二叉查找树的性质,一个满足大根堆的性质。把满足二叉查找树性质的值称作 w,把满足大根堆性质的值称作 prio。 对于 Treap 来说,当前节点的 w 值大于左儿子,小于右儿子。当前节点的 prio 值小于儿子节点的值。

每个节点的 w 我们无法改变,为了保证 Treap 的平衡性,我们需要让每个节点的 prio 都取一个随机值,这样我们就可以保证这棵树“基本平衡”。

随机数的生成

如果我们采用系统函数生成的随机数,会有出现重复的可能性。如果 prio 取到了重复的值,则必然会造成堆结构的混乱。

生成不重复的随机数的办法:

inline int random_f() {
static int seed = 623; // seed 可以随便取?
return seed = int(seed * 48271LL % 2147483647);
}


48271 就可以取遍 1 - 2147483647 中的所有数字。

插入

给节点随机分配一个优先级,先把要插入的点插入到一个叶子上,然后跟维护堆一样,我们维护一个小根堆,如果当前节点的优先级比根大就旋转,如果当前节点是根的左儿子就右旋,如果当前节点是根的右儿子就左旋。

由于旋转是 O(1) 的,最多进行 h 次(h 是树的高度),插入的复杂度是 O(h) 的,在期望情况下 h=O(logn),所以它的期望复杂度是 O(logn)。

其实也就是说,

右旋就是,让当前节点降为自己的右儿子,让左儿子代替自己,并让自己左儿子的右儿子成为自己的左儿子。

左旋相同,就是让当前节点降为自己的左儿子,让右儿子代替自己,并让自己右儿子的左儿子成为自己的右儿子。



删除

因为 Treap 满足堆性质,所以只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。

具体的方法:

如果该节点的左子节点的优先级小于右子节点的优先级,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续操作;

反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,继续操作,直到变成可以直接删除的节点。

(即:让优先级小的结点旋到上面,满足堆的性质)



删除最多进行 O(h) 次旋转,期望复杂度是 O(logn)。

Luogu P3369 【模板】普通平衡树(Treap/SBT)

插入x数

删除x数(若有多个相同的数,因只删除一个)

查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)

查询排名为x的数

求x的前驱(前驱定义为小于x,且最大的数)

求x的后继(后继定义为大于x,且最小的数)

#include <bits/stdc++.h>

using namespace std;
const int N = 2e5 + 5;

inline int random_f() {
static int seed = 623;
return seed = int(seed * 48271LL % 2147483647);
}

struct Treap {
int rt, cnt;
int w
, prio
, size
, lson
, rson
;

inline void clear() {
rt = 0; cnt = 0;
memset(w, 0, sizeof(w));
memset(prio, 0, sizeof(prio));
memset(size, 0, sizeof(size));
memset(lson, 0, sizeof(lson));
memset(rson, 0, sizeof(rson));
}

inline void pushup(int &p) {size[p] = size[lson[p]] + size[rson[p]] + 1;}

inline void right_rorate(int &p) {
int tmp = lson[p];
lson[p] = rson[tmp];
rson[tmp] = p;

size[tmp] = size[p];
pushup(p);
p = tmp;
}

inline void left_rorate(int &p) {
int tmp = rson[p];
rson[p] = lson[tmp];
lson[tmp] = p;

size[tmp] = size[p];
pushup(p);
p = tmp;
}

inline void insert(int &p, int x) {
if(p == 0) {
p = ++ cnt;
size[p] = 1, w[p] = x, prio[p] = random_f();
return ;
}

size[p] ++;
insert((x >= w[p]) ? rson[p] : lson[p], x);
if(prio[lson[p]] < prio[p] && lson[p] != 0) right_rorate(p);
if(prio[rson[p]] < prio[p] && rson[p] != 0) left_rorate(p);
pushup(p);
}

inline void delete_f(int &p, int x) {
size[p] --;

if(w[p] == x) {
if(lson[p] == 0 && rson[p] == 0) {p = 0; return ;}
if(lson[p] == 0 || rson[p] == 0) {p = lson[p] + rson[p]; return ;}

if(prio[lson[p]] < prio[rson[p]]) {
right_rorate(p);
delete_f(rson[p], x);
return ;
} else {
left_rorate(p);
delete_f(lson[p], x);
return ;
}
}

delete_f((x >= w[p]) ? rson[p] : lson[p], x);
pushup(p);
}

inline int rank(int &p, int x) {
if(p == 0) return 0;

int tmp;
if(x > w[p]) tmp = size[lson[p]] + 1 + rank(rson[p], x);
else tmp = rank(lson[p], x);

return tmp;
}

inline int select(int &p, int x) {
if(x == size[lson[p]] + 1) return w[p];

if(x > size[lson[p]] + 1) return select(rson[p], x - size[lson[p]] - 1);
else return select(lson[p], x);
}

inline int querypre(int &p, int x) {
if(p == 0) return 0; // 找不到超过 x 的,就返回 0,tmp 的 0 是在这里赋值的

if(w[p] >= x) return querypre(lson[p], x);
// 此时 w[p] < x,再到 p 的右子树里找一个最大的不超过 x 的,即为答案
int tmp = querypre(rson[p], x);
return (tmp == 0) ? w[p] : tmp;
}

inline int querysuf(int &p, int x) {
if(p == 0) return 0;

if(w[p] <= x) return querysuf(rson[p], x);
int tmp = querysuf(lson[p], x);
return (tmp == 0) ? w[p] : tmp;
}
}treap;

int main() {
int n;
scanf("%d", &n);
treap.clear();

for(int i = 1; i <= n; i ++) {
int opt, x;
scanf("%d%d", &opt, &x);

if(opt == 1) treap.insert(treap.rt, x);
if(opt == 2) treap.delete_f(treap.rt, x);
if(opt == 3) printf("%d\n", treap.rank(treap.rt, x) + 1);
if(opt == 4) printf("%d\n", treap.select(treap.rt, x));
if(opt == 5) printf("%d\n", treap.querypre(treap.rt, x));
if(opt == 6) printf("%d\n", treap.querysuf(treap.rt, x));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: