「Treap」学习笔记

T r e a p Treap Treap学习笔记

简介

t r e a p treap treap,顾名思义,就是 t r e e + h e a p tree+heap tree+heap,是一种常见的平衡树。

前置知识

B S T BST BST性质

给定一棵二叉树,树上的每个节点带有一个权值。

对于树上的任意一个节点,满足:

  • 该节点的权值大于左子树中任意节点的权值。

  • 该节点的权值小于右子树中任意节点的权值。

满足这两条性质的二叉树就是 “二叉查找树”( B S T BST BST

H e a p Heap Heap性质

即堆性质。

以大根堆为例,堆中的每一个节点的权值大于子树中任意节点的权值。

平衡树

普通的 B S T BST BST可以支持插入、删除、检索、求前驱/后继等操作。

在随机数据中, B S T BST BST的表现很优秀,单次操作期望时间复杂度为 O ( l o g 2 n ) O(log_2{n}) O(log2n)

但是,如果数据呈现单调递增的样子,那么 B S T BST BST就会退化成一条链,单次操作 O ( n ) O(n) O(n)

为了让一条链平衡,诞生了许多 “平衡树”Treap就是其中一种。

T r e a p Treap Treap

满足 B S T BST BST性质且中序遍历为相同序列的二叉查找树是不唯一的。而这些二叉查找树都是等价的。

我们可以在保持二叉查找树本质不变的情况下,改变它的形态,让它接近一棵 “平衡树”

改变树的形态的方法就是 “旋转”。“旋转”又分为 “左旋”“右旋”

image

从这张图中,我们可以看出:

以右旋为例,将待旋转节点 A A A旋转至其右子树处,然后将原来 A A A节点左儿子的右子树全部接到现在 A A A节点的左儿子处。

void zig(int & p) {
    int tmp = tree[p].l;
    tree[p].l = tree[tmp].r, tree[tmp].r = p, p = tmp;
    push_up(tree[p].r), push_up(p);
}
void zag(int & p) {
    int tmp = tree[p].r;
    tree[p].r = tree[tmp].l, tree[tmp].l = p, p = tmp;
    push_up(tree[p].l), push_up(p);
}

现在,问题在于如何合理地旋转使得树尽可能“平衡”?

之前提到过,在数据随机的情况下, B S T BST BST的期望时间复杂度为 O ( l o g 2 n ) O(log_2{n}) O(log2n)

我们可以利用“数据随机”的特点创造平衡条件。

对于树上的每一个节点,我们赋予一个随机出来的数值 k e y key key,称为 “键值”.

插入每一个新节点之后,我们自底向上检查,如果 k e y key key不符合 H e a p Heap Heap性质,我们就将其旋转,直至整棵树同时满足 B S T BST BST性质和 H e a p Heap Heap性质。

对于删除操作,我们可以在找到待删除节点之后将其旋转至叶子节点再删除。

对于维护的信息,可能会出现重复的权值。于是对于相同权值我们只记录一次,同时我们记录它的出现次数。

我们可以在一开始就加入 i n f inf inf, − i n f -inf inf两个数值,避免边界情况的处理。

于是,我们就可以在期望 O ( l o g 2 n ) O(log_2{n}) O(log2n)的时间内实现检索、插入、删除、求前驱/后继。

Code

模板(普通平衡树)为例,给出 T r e a p Treap Treap的代码:

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
using namespace std;
const int inf = 0x3f3f3f3f, maxn = 100010;
int n, tot, root = 1;
struct treap {
    int l, r, size, cnt, val, key;
} tree[maxn];

void push_up(int u) {
    tree[u].size = tree[tree[u].l].size + tree[tree[u].r].size + tree[u].cnt;
}

int New(int val) {
    tree[++tot].val = val, tree[tot].key = rand(), tree[tot].cnt = tree[tot].size = 1;
    return tot;
}
void build() {
    New(-inf), New(inf);
    tree[1].r = 2;
    push_up(1);
}

void zig(int & p) {
    int tmp = tree[p].l;
    tree[p].l = tree[tmp].r, tree[tmp].r = p, p = tmp;
    push_up(tree[p].r), push_up(p);
}
void zag(int & p) {
    int tmp = tree[p].r;
    tree[p].r = tree[tmp].l, tree[tmp].l = p, p = tmp;
    push_up(tree[p].l), push_up(p);
}

void insert(int & p, int val) {
    if(p == 0) {
        p = New(val);
        return;
    }
    if(val == tree[p].val) {
        tree[p].cnt++, tree[p].size++;
        return;
    }
    if(val < tree[p].val) {
        insert(tree[p].l, val);
        if(tree[p].key < tree[tree[p].l].key) zig(p);
    }
    else {
        insert(tree[p].r, val);
        if(tree[p].key < tree[tree[p].r].key) zag(p);
    }
    push_up(p);
}
void remove(int & p, int val) {
    if(p == 0) return;
    if(val == tree[p].val) {
        if(tree[p].cnt > 1) {
            tree[p].cnt--, tree[p].size--;
            return;
        }
        if(tree[p].l || tree[p].r) {
            if(tree[p].r == 0 || tree[tree[p].l].key > tree[tree[p].r].key) zig(p), remove(tree[p].r, val);
            else zag(p), remove(tree[p].l, val);
            push_up(p);
        }
        else p = 0;
        return;
    }
    if(val < tree[p].val) remove(tree[p].l, val);
        else remove(tree[p].r, val);
    push_up(p);
}

int get_rank(int p, int val) {
    if(p == 0) return 0;
    if(tree[p].val == val) return tree[tree[p].l].size + 1;
    if(val < tree[p].val) return get_rank(tree[p].l, val);
    return get_rank(tree[p].r, val) + tree[tree[p].l].size + tree[p].cnt;
}
int get_val(int p, int rank) {
    if(p == 0) return inf;
    if(tree[tree[p].l].size >= rank) return get_val(tree[p].l, rank);
    if(tree[tree[p].l].size + tree[p].cnt >= rank) return tree[p].val;
    return get_val(tree[p].r, rank - tree[tree[p].l].size - tree[p].cnt);
}

int pre(int val) {
    int ret = 1, p = root;
    while(p) {
        if(val == tree[p].val) {
            if(tree[p].l > 0) {
                p = tree[p].l;
                while(tree[p].r) p = tree[p].r;
                ret = p;
            }
            break;
        }
        if(tree[p].val < val && tree[p].val > tree[ret].val) ret = p;
        if(val < tree[p].val) p = tree[p].l; else p = tree[p].r;
    }
    return tree[ret].val;
}
int next(int val) {
    int ret = 2, p = root;
    while(p) {
        if(val == tree[p].val) {
            if(tree[p].r > 0) {
                p = tree[p].r;
                while(tree[p].l) p = tree[p].l;
                ret = p;
            }
            break;
        }
        if(tree[p].val > val && tree[p].val < tree[ret].val) ret = p;
        if(val < tree[p].val) p = tree[p].l; else p = tree[p].r;
    }
    return tree[ret].val;
}

int main()
{
    srand((unsigned int) time(NULL));
    build();
    scanf("%d", &n);
    while(n--) {
        int opt, x;
        scanf("%d %d", &opt, &x);
        if(opt == 1) insert(root, x);
        if(opt == 2) remove(root, x);
        if(opt == 3) printf("%d\n", get_rank(root, x) - 1);
        if(opt == 4) printf("%d\n", get_val(root, x + 1));
        if(opt == 5) printf("%d\n", pre(x));
        if(opt == 6) printf("%d\n", next(x));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值