平衡树 treap 模板

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
//treap 就是tree+heap 利用了二叉堆的结构整体趋于平衡树(不一定是严格意义上的平衡)
//又利用了二叉平衡树的排序 才让查找 插入的效率在logn 
struct data{//该树的标号为完全二叉树标号方式 
    int l,r,v,size,rnd,w;//分别表示左右子树的序号,节点的值,
	//以该节点为根左右子树加上该点总共有多少值,节点的随机值(是建成二叉堆的依据),等于v的值有几个(因为可能有好几个相同的值) 
}tr[100005];
int n,size,root,ans;
void update(int k)//更新结点信息
{
    tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w;
}
void rturn(int &k)//参考左旋 
{
    int t=tr[k].l;tr[k].l=tr[t].r;tr[t].r=k;
    tr[t].size=tr[k].size;update(k);k=t;
}
void lturn(int &k)//左旋 
{
    int t=tr[k].r;
	tr[k].r=tr[t].l;//该点的右子树变成原来右子树的左子树 
	tr[t].l=k;//原来的右子树的左子树为该点 
    tr[t].size=tr[k].size;//原来右子树的大小就是为该点值的大小(好好考虑下 因为旋转了这棵树总的大小还是不变的) 
	update(k);//更新该点信息 
	k=t;//因为左旋后位置发生了改变 原来右子树的点到了该节点的位置 所以k赋值为t 
}
void insert(int &k,int x)//使用引用可直接赋值 
{     //插入肯定会最后被安排到叶子节点 
    if(k==0)//若已经到了叶子节点 
    {
        size++;k=size;//总节点数加一 给该节点标号 
        tr[k].size=tr[k].w=1;//该点的大小和w值都赋值为1 
		tr[k].v=x;
		tr[k].rnd=rand();//产生随机数并赋值 
        return;
    }
    tr[k].size++;//插入一个数 所查找经过的值都加一 因为从上往下走 一定是它的子孙会被插入添加 所以该节点也要修改 
    if(tr[k].v==x)tr[k].w++;//插入的数已经存在且就是该树 则直接在w上加一 
    else if(x>tr[k].v)//如果大于该节点值 
    {
        insert(tr[k].r,x);//向右寻找要插入的点 
        if(tr[tr[k].r].rnd<tr[k].rnd)lturn(k);//回溯的过程中如果右子树的随机值小于该随机值 则左旋 
		//而且二叉树的左旋和右旋并不影响二叉排序树的性质 又可以调整二叉堆的顺序 
    }
    else 
    {
        insert(tr[k].l,x);
        if(tr[tr[k].l].rnd<tr[k].rnd)rturn(k);
    } 
}
/*
情况一, 该节点为叶节点或链节点(即只有一个孩子节点的节点),则该节点是可以直接删除的节点。若该节点有非空子节点,用非空子节点代替该节点的,否则用空节点代替该节点,然后删除该节点。

情况二, 该节点有两个非空子节点。我们的策略是通过旋转,使该节点变为可以直接删除的节点。如果该节点的左子节点的修正值小于右子节点的修正值,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续讨论;反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,继续讨论,直到变成可以直接删除的节点。(也就是让该节点的左右孩子节点中的最小优先级的节点称为该节点)*
*/ 
void del(int &k,int x)
{
    if(k==0)return; //到叶子节点直接返回 
    if(tr[k].v==x)
    {
        if(tr[k].w>1)//要删的值有好几个 
        {
            tr[k].w--;tr[k].size--;return;//直接把计数减少 
        }
        if(tr[k].l*tr[k].r==0)k=tr[k].l+tr[k].r;//如果该节点是只有一个子节点或者没有子节点 该节点直接被子节点替代 
        else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd)//选择左右子树中最小的一个节点作为根节点 
            rturn(k),del(k,x);//先旋转 再看是否满足删除的条件 
        else lturn(k),del(k,x);
    }
    else if(x>tr[k].v)//查找 该路径上的值都要减一 
        tr[k].size--,del(tr[k].r,x);
    else tr[k].size--,del(tr[k].l,x);
}
int query_rank(int k,int x)//查询x的排名  就是把小于自己的树(左子树)的综合加上自身(加1) 
{
    if(k==0)return 0; 
    if(tr[k].v==x)return tr[tr[k].l].size+1;//找到了该节点 把左子树的个数加上自身的个数(只加一 因为只要求最小的那个值)  
    else if(x>tr[k].v)//如果大于该数将此节点的左子树和本身节点的w加起来再去右子树中找小于该数的数 
        return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r,x);
    else return query_rank(tr[k].l,x);//如果x小于当前节点从左子树找 
}
int query_num(int k,int x)//查询排名为x的数 
{
    if(k==0)return 0;
    if(x<=tr[tr[k].l].size)//如果x比左子树的总和小就往左子树里找 
        return query_num(tr[k].l,x);
    else if(x>tr[tr[k].l].size+tr[k].w)// 如果比该节点的左子树和该点w的总和大往右子树找第x-tr[tr[k].l].size-tr[k].w个值 
        return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
    else return tr[k].v;//刚好缩到为1的时候的那个值就是查的那个值 
}
void query_pro(int k,int x)//查找该x的前驱 小于x的最大值 
{
    if(k==0)return;
    if(tr[k].v<x)//查找遍历的过程中记录路径上小于x的值 最后一个一定是小于他的最大值 
    {
        ans=k;//ans记录最优解 
		query_pro(tr[k].r,x);
    }
    else query_pro(tr[k].l,x);
}
void query_sub(int k,int x)//同上 
{
    if(k==0)return;
    if(tr[k].v>x)
    {
        ans=k;query_sub(tr[k].l,x);
    }
    else query_sub(tr[k].r,x);
}
int main()
{
    scanf("%d",&n);
    int opt,x;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&opt,&x);
        switch(opt)
        {
        case 1:insert(root,x);break; //root=1 
        case 2:del(root,x);break;
        case 3:printf("%d\n",query_rank(root,x));break;
        case 4:printf("%d\n",query_num(root,x));break;
        case 5:ans=0;query_pro(root,x);printf("%d\n",tr[ans].v);break;
        case 6:ans=0;query_sub(root,x);printf("%d\n",tr[ans].v);break;
        }
    }
    return 0;
}

参考:

https://www.cnblogs.com/MyStringIsNotNull/p/9165675.html 这里有很多图可以参考 特别是左右旋

https://baike.baidu.com/item/Treap/4321536?fr=aladdin

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值