FHQ Treap(无旋treap)学习笔记

FHQ Treap

就是非旋Treap啦

treap=tree+heap,既满足二叉搜索树的性质也满足堆的性质

是一个非常好用的平衡树,实现不难,常数一般,还可以可持久化

特别有意思。

这个玩意儿有什么用呢?

就是平衡树的作用啊

反正我觉得这个东西比普通treap有用,比splay好理解而且好写

详细的实现可以看下面

FHQ_treap的实现

FHQ_treap可以实现以下几个基本操作:

1、在某个序列中插入一个数

2、在某个序列中删除一个数

3、查询排名为i的数的值

4、查询值为i的数的排名

5、求i的前驱(定义为比i小且最大的数)

6、求i的后继(比i大且最小的数)

我们通过一道模板来看一看怎么写这个平衡树吧

洛谷P3369 【模板】普通平衡树

定义

首先看一下这个FHQ_treap的节点的定义

struct treap{
    int lch,rch;//左右儿子
    int siz;//子树大小(儿子节点数量)
    int key;//key值满足堆的性质
    int val;//每个节点存储的权值,满足二叉搜索树的性质
}T[200001];

其中的key值是一个随机的值。

在这棵平衡树中,左儿子的val值都比自己低,右儿子都比自己高

对于子树大小,我们有如下的维护代码:

void update(int x){
    T[x].siz=T[T[x].lch].siz+T[T[x].rch].siz+1;
}

维护子树大小是为了查询排名

所以为什么要用key维护堆的性质呢?这样可以让整棵树的层数变小,尽量靠近logn,然后操作时就可以快一些

接下来看看基本操作怎么实现吧

基本操作:split和merge

首先说一下split

split就是把原来的一棵大树分成两棵小树,其中左边的小树的值全部小于右边的小树

split的思想就是按照val值来分裂,详细的实现可以看代码

void split(int now,int &a,int &b,int val){//now是准备要分裂的节点,a和b是两棵小树,a小树的val值全部小于等于val,b小树的val值全部大于val
    if(now==0){//如果没得分了,直接停止返回就好了
        a=b=0;
        return;
    }
    if(T[now].val<=val){//如果现在这个节点的val值是小于或等于val的,我们把它分到a小树里头
        a=now;
        split(T[now].rch,T[a].rch,b,val);//然后把它的右子树,即比它的val值还要大的节点,拆成a的右子树以及b,因为a的右子树和b都比当前节点的val值还要大
    }
    else {//否则就反之嘛,一样的
        b=now;
        split(T[now].lch,a,T[b].lch,val);
    }
    update(now);//维护子树大小
}

然后是merge。

merge就是把两棵树合并到同一棵树上去

但是我们发现,如果直接在中间添加一个根节点然后连起来,很可能会不满足堆的性质

所以我们按照key值来合并,详细实现代码如下:

void merge(int &now,int a,int b){//now是我合并出来的新节点
    if(a==0||b==0){
        now=a+b;//直接当成a或b
        return;
    }
    if(T[a].key<T[b].key){//按key值合并以维护堆的性质
        now=a;//如果a的key值比b的key值小,我们就把a的右儿子(即val值比a大的元素)与b继续合并
        merge(T[now].rch,T[a].rch,b);
    }
    else {
        now=b;
        merge(T[now].lch,a,T[b].lch);//反之亦然
    }
    update(now);//一样的,维护子树大小
}
组合操作

1、插入与删除

插入的话我们就新建一个节点,设其值为val,然后把原树拆成两棵,一棵小于等于val,一棵大于val,

然后把新节点与小于等于val的树拼起来,最后整一个拼起来就好了。

删除,设这个要删除的节点为val,那么我们把原树拆成两棵,一个小于等于val一个大于val,

然后在小于等于val的树上面拆成一棵小于等于val-1的一棵大于val-1(即等于val的)

然后假装等于val的那个节点不存在,把原本等于val的那棵树的左儿子和右儿子连起来,中间等于val的节点就不见了

最后全部树连起来即可

void insert(int val){
    int x=0,y=0,z=newnode(val);
    split(root,x,y,val);
    merge(x,x,z);
    merge(root,x,y);
}
void del(int val){
    int x=0,y=0,z=0;
    split(root,x,y,val);
    split(x,x,z,val-1);
    merge(z,T[z].lch,T[z].rch);
    merge(x,x,z);
    merge(root,x,y);
}

2、查排名/权值

查排名,我们把原树拆成两棵,一棵小于val一棵大于等于val

然后我们惊奇地发现val的排名就是小于val的节点的数量+1

最后记得要再拼回去

查权值:

对于当前节点,如果它的左子树的siz+1刚好是rank,那么这个节点的排名就是rank,刚好是我们想要的

否则,假如siz比rank大,我们就进入右子树找,反之进入左子树找

int find(int now,int rank){
    while(T[T[now].lch].siz+1!=rank){
        if(T[T[now].lch].siz>=rank)now=T[now].lch;
        else {
            rank-=(T[T[now].lch].siz+1);//为什么要减可以自己想一想
            now=T[now].rch;
        }
    }
    return T[now].val;
}
int getrank(int val){
    int x=0,y=0;
    split(root,x,y,val-1);
    int ans=T[x].siz+1;
    merge(root,x,y);
    return ans;
}
int getval(int rank){
    return find(root,rank);
}

3、求前驱/后继

首先找前驱,我们拆成两棵树,然后在权值小于等于val-1的那棵树上找最大的即可

后继就在权值大于val的那棵树上找到最小的就可以了

int getpre(int val){
    int x=0,y=0;
    split(root,x,y,val-1);
    int ans=find(x,T[x].siz);
    merge(root,x,y);
    return ans;
}
int getnxt(int val){
    int x=0,y=0;
    split(root,x,y,val);
    int ans=find(y,1);
    merge(root,x,y);
    return ans;
}
其他的东西(注意项)

1、一开始一定要新建一个虚拟的根节点

2、假如我们要维护的序列是有顺序的,那么我们就把val值改一改,改成位置就好了

完整代码
#include<bits/stdc++.h>
using namespace std;
#define xyjakioi 0x7fffffff
struct treap{
    int lch,rch;
    int siz;
    int key;
    int val;
}T[200001];
int tot;
int root=1;
int seed=19260817;
int ran(){
    return seed=int(seed*2333ll%2147483647);
}
int newnode(int val){
    T[++tot].siz=1;
    T[tot].val=val;
    T[tot].key=ran();
    T[tot].lch=0;
    T[tot].rch=0;
    return tot;
}
void update(int x){
    T[x].siz=T[T[x].lch].siz+T[T[x].rch].siz+1;
}
void split(int now,int &a,int &b,int val){
    if(now==0){
        a=b=0;
        return;
    }
    if(T[now].val<=val){
        a=now;
        split(T[now].rch,T[a].rch,b,val);
    }
    else {
        b=now;
        split(T[now].lch,a,T[b].lch,val);
    }
    update(now);
}
void merge(int &now,int a,int b){
    if(a==0||b==0){
        now=a+b;
        return;
    }
    if(T[a].key<T[b].key){
        now=a;
        merge(T[now].rch,T[a].rch,b);
    }
    else {
        now=b;
        merge(T[now].lch,a,T[b].lch);
    }
    update(now);
}
int find(int now,int rank){
    while(T[T[now].lch].siz+1!=rank){
        if(T[T[now].lch].siz>=rank)now=T[now].lch;
        else {
            rank-=(T[T[now].lch].siz+1);
            now=T[now].rch;
        }
    }
    return T[now].val;
}
void insert(int val){
    int x=0,y=0,z=newnode(val);
    split(root,x,y,val);
    merge(x,x,z);
    merge(root,x,y);
}
void del(int val){
    int x=0,y=0,z=0;
    split(root,x,y,val);
    split(x,x,z,val-1);
    merge(z,T[z].lch,T[z].rch);
    merge(x,x,z);
    merge(root,x,y);
}
int getrank(int val){
    int x=0,y=0;
    split(root,x,y,val-1);
    int ans=T[x].siz+1;
    merge(root,x,y);
    return ans;
}
int getval(int rank){
    return find(root,rank);
}
int getpre(int val){
    int x=0,y=0;
    split(root,x,y,val-1);
    int ans=find(x,T[x].siz);
    merge(root,x,y);
    return ans;
}
int getnxt(int val){
    int x=0,y=0;
    split(root,x,y,val);
    int ans=find(y,1);
    merge(root,x,y);
    return ans;
}
int main(){
    //freopen("in.txt","r",stdin);
    newnode(xyjakioi);//必须,一定,肯定,要新建节点!
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        int opt,x;
        scanf("%d%d",&opt,&x);
        if(opt==1){
            insert(x);
        }
        if(opt==2){
            del(x);
        }
        if(opt==3){
            printf("%d\n",getrank(x));
        }
        if(opt==4){
            printf("%d\n",getval(x));
        }
        if(opt==5){
            printf("%d\n",getpre(x));
        }
        if(opt==6){
            printf("%d\n",getnxt(x));
        }
    }
}

总结

FHQ_treap是一个好理解、好实现、常数不算大的平衡树,非常推荐大家学哦

是进阶数据结构的起点,成长的神犇的必经之路!

更深层的应用

占坑。

转载于:https://www.cnblogs.com/youddjxd/p/11058364.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值