Loading

平衡树——fhq_treap

之前写过 Splay 的介绍。那么接下来就是对于 fhq_treap 的介绍了。

0. 引子

dalao云:“treap,tree+heap 也。”
treap 是一个同时具有 tree(即BST)和 heap 的特点的平衡树。
具体地说,我们会对每个节点随机一个 key 值,然后要求这棵 BST 要对 key 值同时满足堆的性质。也就是说,treap 是笛卡尔树的一种。
通过随机的 key 值,我们可以某种程度上随机确定树的形态,从而使树尽量平衡。

传统的 treap 是靠旋转来确定树的形态。但是 fhq_treap 则大大不同。它的核心操作为 split 和 merge。
首先我们先来看一棵 fhq_treap 需要存储的信息,和一些基础操作。

int tot,rt;
struct node{int l,r,val,key,siz;}tree[maxn];
inline void update(int x){tree[x].siz=tree[tree[x].l].siz+tree[tree[x].r].siz+1;}//更新 siz
inline void newnode(int &x,int val){x=++tot;tree[x]=(node){0,0,val,rand(),1};}

基本上没什么好说的。注意 fhq_treap 不需要像 Splay 一样将 val 相同的结点合并。

1. split/merge

split 有两种,按权值分裂和按大小分裂。不过这里只讲按权值分裂,按大小分裂参见文艺平衡树的 fhq_treap 做法
split,顾名思义,就是将一棵树分成两个。具体地,我们将一个 fhq_treap 分成两个 fhq_treap,使得一棵树上的权值都小于等于给定的值,另一棵树上的权值都大于给定的值。
merge 只有一种,顾名思义就是将 split 后的两棵树合成一个。

直接放代码:

inline void split(int x,int val,int &rx,int &ry)//将树 x 按权值 val 分裂成 rx 和 ry
{
    if(!x){rx=ry=0;return;}// 若当前树为空则无法分裂,直接返回
    if(tree[x].val<=val)
    {
        rx=x;//若当前结点的值小于等于 val,那么这个结点及其左子树就一定属于 rx
        split(tree[x].r,val,tree[x].r,ry);//递归地分裂右子树,并且把右子树分裂出的左子树作为 rx 的右子树
    }
    else{ry=x;split(tree[x].l,val,rx,tree[x].l);}//同理
    update(x);//记得要 update
}
inline int merge(int x,int y)//返回合并后的树 x 和树 y
{
    if(!x||!y)return x+y;//如果一棵树为空直接返回另一棵树
    if(tree[x].key<tree[y].key)//注意,为了保证 fhq_treap 的平衡,我们需要按照 key 值进行合并(这也是 fhq_treap 的 key 值唯一有作用的地方。)
    //注:虽然 key 的值是随机的,但是不可以把这句话改成 rand()&1,这会使性能下降。
    //事实上使用 key 值的另一个作用是使小的树尽可能合并到大的树上面,这样可以使树高尽量小。但是单纯 rand 就没有这个作用。
    {
        tree[x].r=merge(tree[x].r,y);//选择将 x 的右子树和 y 合并作为新的右子树
        update(x);//记得 update
        return x;
    }
    else{tree[y].l=merge(x,tree[y].l);update(y);return y;}//同理
}

2. 剩余操作

有了上面两个基础操作,实现剩下的操作就简单了。

void ins(int val)//插入结点
{
    int now,x,y;
    split(rt,val,x,y);//按权值分裂
    newnode(now,val);
    rt=merge(merge(x,now),y);//将新建的结点插到中间
}
void del(int val)//删除对应值的一个结点
{
    int x,y,z;
    split(rt,val,x,z);split(x,val-1,x,y);//首先把权值为 val 的结点全部给分离出来
    y=merge(tree[y].l,tree[y].r);//合并左右子树,就相当于删除了根结点
    rt=merge(merge(x,y),z);//再合并回去
}
int getrank(int val)//根据权值查排名
{
    int rk,x,y;split(rt,val-1,x,y);
    rk=tree[x].siz+1;//显然,权值小于等于 val-1 的数的个数加一就是答案
    rt=merge(x,y);
    return rk;
}
//剩下的都是平衡树的常规操作了
int getnum(int rk)
{
    int now=rt;
    while(now)
    {
        int lsiz=tree[tree[now].l].siz;
        if(lsiz+1==rk)break;
        else if(rk<=lsiz)now=tree[now].l;
        else{rk-=lsiz+1;now=tree[now].r;}
    }
    return tree[now].val;
}
int getpre(int val){return getnum(getrank(val)-1);}
int getsuc(int val){return getnum(getrank(val+1));}
posted @ 2022-02-08 23:51  pjykk  阅读(203)  评论(0编辑  收藏  举报