平衡树之FHQ Treap(无旋)

2022年01月15日,第二天

平衡树—— F H Q   T r e a p FHQ\ Treap FHQ Treap(无旋)

学习 f h q   T r e a p fhq\ Treap fhq Treap 之前,要先了解普通的 T r e a p Treap Treap 是怎么回事。

T r e a p Treap Treap ,就是 T r e e Tree Tree H e a p Heap Heap 。 它让平衡树上的每一个结点存放两个信息:值和一个随机的索引。其中值满足二叉搜索树的性质,索引满足堆的性质,结合二叉搜索树和二叉堆的性质来使树平衡。这也是 T r e a p Treap Treap 的性质。

T r e a p Treap Treap 为什么可以平衡?我们都知道,如果对一颗二叉搜索树进行插入的值按次序是有序的,那么二叉搜索树就会退化成一个链表。那么我们就可以别让数值按次序插入,一个很好的方法就是把插入的数值随机化,原因是大量数据时,随机化后变成有序的概率几乎为零。

T r e a p Treap Treap 用二叉堆来维护随机索引,其实就是相当于把插入次序随机化。插入一数值后你必然要让索引满足二叉堆的特性,但又因为索引是随机的,那就会导致插入的数不知道搞到了哪里去了,相当于插入次序随机了。

二叉搜索树的性质:当前结点左子树的值都比当前结点的值小,右子树的值都不比当前结点小,也就是大于等于。

二叉堆的性质:父结点的 优先级 总是大于或等于任何一个子节点的 优先级

普通的 T r e a p Treap Treap 用来维护树平衡的核心操作是树旋转,而 f h q   T r e a p fhq\ Treap fhq Treap 的核心操作有两个,分别是 分裂 ( s p l i t ) (split) (split)合并 (merge) ,常数稍大。

结点, f h q   T r e a p fhq\ Treap fhq Treap 的结点需要维护一下五个信息:

  • 左右子树编号
  • 元素的值
  • 索引
  • 子树大小
struct node {
    int l, r;
    int val, key;
    int size;
}fhq[N];
int cnt, root;
#include <random>
mt19937 rnd(233);
inline int newnode(int val) {
    fhq[++ cnt].val = val;
    fhq[cnt].key = rnd();
    fhq[cnt].size = 1;
    return cnt;
}
核心操作——分裂 ( s p l i t ) (split) (split)

分裂有两种:按值分裂和按大小分裂。

按值分裂:把树拆成两颗树,拆出来的一棵树的值全部小于等于给定的值,另外一部分的值全部大于给定的值。

按大小分裂:把树拆成两颗树,其中一颗树的大小等于给定的大小,剩余部分在另一颗数里。

一般来说,我们在使用 f h q   T r e a p fhq\ Treap fhq Treap 当一颗正常的平衡树用的时候,使用按值分裂,而在维护区间信息的时候,使用按大小分裂。很经典的例子就是 文艺平衡树

inline void pushup(int now) {
    fhq[now].size = fhq[fhq[now].l].size + fhq[fhq[now].r].size + 1;
}
void split (int now, int val, int& x, int& y) {
    if (! now) x = y = 0;
    else {
        if (fhq[now].val <= val) {
            x = now;
            split(fhq[now].r, val, fhq[now].r, y);
        } else {
            y = now;
            split(fhq[now].l, val, x, fhq[now].l);
        }
        pushup(now);
    }
}
核心操作——合并 ( m e r g e ) (merge) (merge)

本质上是分裂的逆向操作。

int merge(int x, int y) {
    if (! x || ! y) return x | y;
    if (fhq[x].key > fhq[y].key) {
        fhq[x].r = merge(fhq[x].r, y);
        pushup(x);
        return x;
    } else {
        fhq[y].l = merge(x, fhq[y].l);
        pushup(y);
        return y;
    }
}
第一个操作:插入

设插入的值为 v a l val val ,那么我们的步骤就是:按值 v a l val val 把树分裂成 x x x y y y ,合并 x x x ,新结点, y y y

可以这么做的原因:我们要插入 v a l val val 这个值,那么我们按值 v a l val val 分裂,于是得到了两颗树 x x x y y y ,按照按值分裂的定义,我们分裂出来的 x x x 树上的所有值一定都小于等于 v a l val val y y y 树上的所有值一定都大于 v a l val val ,那么我们就可直接合并 x x x ,用值 v a l val val 新建的新结点, y y y 就可以了。

int x, y, z;
inline void insert (int val) {
    split (root, val, x, y);
    root = merge (merge (x, newnode(val)), y);
}
第二个操作:删除

设要删除的值为 v a l val val ,那么:首先,按值 v a l val val 把树分裂成 x x x z z z ,再按值 v a l − 1 val - 1 val1 把树分裂成 x x x y y y ,那么此时 y y y 树上的所有值都是等于 v a l val val 的,我们去掉它的根结点:让 y y y 等于合并 y y y 的左子树和 y y y 的右子树。

inline void del (int val) {
    split (root, val, x, z);
    split (x, val - 1, x, y);
    y = merge (fhq[y].l, fhq[y].r);
    root = merge (merge (x, y), z);
}
第三个操作:查询值的排名

设要查询的值为 v a l val val ,那么:按值 v a l − 1 val-1 val1 分裂成 x x x y y y ,则 x x x 的大小 + 1 +1 +1 就是 v a l val val 的排名,最后将 x x x y y y 合并起来。

inline int getrank (int val) {
    split (root, val - 1, x, y);
    int ans = fhq[x].size + 1;
    root = merge (x, y);
    return ans;
}
第四个操作:查询排名的值

和替罪羊树的做法一样。

inline int getnum (int rank) {
    int now = root;
    while (now) {
        if (fhq[fhq[now].l].size + 1 == rank) 
            break;
        else if (fhq[fhq[now].l].size >= rank) 
            now = fhq[now].l;
        else {
            rank -= fhq[fhq[now].l].size + 1;
            now = fhq[now].r;
        }
    }
    return fhq[now].val;
}
第五个操作:前驱和后继

设操作数为 v a l val val ,前驱:按值 v a l − 1 val-1 val1 分裂成 x x x y y y ,则 x x x 里面最右的数就是 v a l val val 的前驱。后继:按值 v a l val val 分裂成 x x x y y y ,则 y y y 里面最左的数就是 v a l val val 的后继。

inline int pre (int val) {
    split (root, val - 1, x, y);
    int now = x;
    while (fhq[now].r) 
        now = fhq[now].r;
    int ans = fhq[now].val;
    root = merge (x, y);
    return ans;
}
inline int nxt (int val) {
    split (root, val, x, y);
    int now = y;
    while (fhq[now].l)
        now = fhq[now].l;
    int ans = fhq[now].val;
    root = merge (x, y);
    return ans;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值