平衡树1---treap

treap是二叉搜索树和堆的结合,所以先来说一下二叉搜索树。

二叉搜索树有如下性质:

【1】当前节点权值大于左儿子的权值

【2】当前节点权值小于右儿子的权值

【3】中序遍历得到的序列递增

通常一棵二叉搜索树没有重复的节点,如果值相同会累计在一个点上

作用:动态维护一个有序序列

常用基本功能有8个,其中插入、删除、找前驱后继、找最大值最小值都可以用set解决,还有四个功能是求某个值的排名、求排名为k的数是哪个,和lower_bound、upper_bound的实现

另外,还通常用到pushup来更新信息,用旋转操作来维护堆的性质

旋转操作如下:

(a,b,c表示子树部分)

旋转后,仍然满足中序遍历序列递增的性质,中序遍历序列不变 ,都为 ayzxc

可以看出,旋转父节点x,可以降低父节点的层数,可方便删除,右旋使左儿子变为根,左旋使右儿子变为根

对于某些二叉搜索树,可能出现极限情况如都在一条链上,而一棵随机的二叉搜索树的期望值为logn

所以将堆的性质加入进来,通过堆来维护随机值,使二叉搜索树变得随机。

目录

插入

删除

找前驱后继(lower_bound,upped_bound)

求某个值的排名

求排名为k的数是哪个

完整代码


在treap中每个节点维护的信息:

Node
{
    int l,r;
    int key;//关键字,满足二叉搜索树的性质
    int val;//随机值,满足大根堆的性质
}

插入

从根节点开始找,如果节点 key 大于所要插入的 key ,递归左子树,否则递归右子树,相等则累加 cnt

void insert(int &p, int key)
{
    if (!p) //空的直接创建
    p = get_node(key);
    else if (tr[p].key == key) 
    tr[p].cnt ++ ;
    else if (tr[p].key > key)
    {
        insert(tr[p].l, key);
        if (tr[tr[p].l].val > tr[p].val) //维护堆性质
        zig(p);
    }
    else
    {
        insert(tr[p].r, key);
        if (tr[tr[p].r].val > tr[p].val) zag(p);
    }
    pushup(p);//如果有需要更新的信息需要pushup
}

删除

叶子节点可以直接删除,非叶子节点可以通过旋转操作变为叶子节点后删除

void remove(int &p, int key)
{
    if (!p) return;
    if (tr[p].key == key)
    {
        if (tr[p].cnt > 1) 
        tr[p].cnt -- ;
        else if (tr[p].l || tr[p].r)
        {
            //只有左儿子大于右儿子右旋上去才不会破坏堆的性质
            if (!tr[p].r || tr[tr[p].l].val > tr[tr[p].r].val)
            {
                zig(p);
                remove(tr[p].r, key);
            }
            else//只有左儿子小于右儿子左旋上去才不会破坏堆的性质
            {
                zag(p);
                remove(tr[p].l, key);
            }
        }
        else p = 0;
    }
    else if (tr[p].key > key)//递归左
    remove(tr[p].l, key);
    else //递归右
    remove(tr[p].r, key);

    pushup(p);
}

找前驱后继(lower_bound,upped_bound)

这里的前驱后继指的是在中序遍历中的前一个位置和后一个位置,寻找时充分利用二叉搜索树的性质

以找前驱为例:

如果当前点存在左子树,那么就走到它的左儿子,从左儿子一直向右找的最后一个值就是当前点中序遍历的前驱

如果没有左子树,那么就看它的父亲,如果存在前驱,则此点必定是父亲的右儿子,且父节点的左子树中的所有节点都小于父节点,所以父节点就是当前点的前驱

int get_prev(int p, int key)   // 找到严格小于key的最大数
{
    if (!p) return -INF;
    if (tr[p].key >= key) return get_prev(tr[p].l, key);
    return max(tr[p].key, get_prev(tr[p].r, key));
}

求某个值的排名

通过递归实现

int get_rank_by_key(int p, int key)    // 通过数值找排名
{
    if (!p) return 0;  
    if (tr[p].key == key) 
    return tr[tr[p].l].size + 1;//左边都是小的
    if (tr[p].key > key)
    return get_rank_by_key(tr[p].l, key);
    return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, key);
}

求排名为k的数是哪个

通过递归实现

int get_key_by_rank(int p, int rank)   // 通过排名找数值
{
    if (!p) return INF; 
    if (tr[tr[p].l].size >= rank) return get_key_by_rank(tr[p].l, rank);
    //下面的意思是tr[tr[p].l].size不够,加上当前数的个数才够,那么答案是当前数
    if (tr[tr[p].l].size + tr[p].cnt >= rank) 
    return tr[p].key;
    return get_key_by_rank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

完整代码

附上一道模板题:253. 普通平衡树 - AcWing题库 

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010,INF=1e8;
int n;
struct node
{
	int l,r;
	int key,val;
	int cnt,size;
}tr[N];
int root,idx;
void pushup(int p)
{
	tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].cnt;
} 
int get_node(int key)
{
	tr[++idx].key=key;//开一个新点
	tr[idx].val=rand();
	tr[idx].cnt=tr[idx].size=1;//默认是叶子节点
	return idx; 
}
void zig(int &p)//右旋
{
	int q=tr[p].l;
	tr[p].l=tr[q].r,tr[q].r=p,p=q;
	pushup(tr[p].r),pushup(p);
} 
void zag(int &p)
{
	int q=tr[p].r;
	tr[p].r=tr[q].l,tr[q].l=p,p=q;
	pushup(tr[p].l),pushup(p);
}
void build()
{
	//两个哨兵
	get_node(-INF),get_node(INF);
	root=1,tr[1].r=2;
	pushup(root);
	if(tr[1].val<tr[2].val)
	zag(root);
}
void insert(int &p,int key)
{
	if(!p)
	p=get_node(key);
	else if(tr[p].key==key)
	tr[p].cnt++;
	else if(tr[p].key>key)
	{
		insert(tr[p].l,key);
		if(tr[tr[p].l].val>tr[p].val)
		zig(p);
	}
	else
	{
		insert(tr[p].r,key);
		if(tr[tr[p].r].val>tr[p].val)
		zag(p);
	}
	pushup(p);
}
void remove(int &p,int key)
{
	if(!p)
	return;
	if(tr[p].key==key)
	{
		if(tr[p].cnt>1)
		tr[p].cnt--;
		else if(tr[p].l||tr[p].r)
		{
			if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
			{
				zig(p);
				remove(tr[p].r,key);
			}
			else
			{
				zag(p);
				remove(tr[p].l,key);
			}
		}
		else//叶子节点直接删除 
		p=0;
		
	}
	else if(tr[p].key>key)
		remove(tr[p].l,key);
	else
		remove(tr[p].r,key);
	pushup(p); 
}
int get_rank_by_key(int p,int key)//通过数值找排名
{
	if(!p)
	return 0;
	if(tr[p].key==key)
	return tr[tr[p].l].size+1;
	if(tr[p].key>key)
	return get_rank_by_key(tr[p].l,key);
	return tr[tr[p].l].size+tr[p].cnt+get_rank_by_key(tr[p].r,key);//最后是右子树的排名 
} 
int get_key_by_rank(int p,int rank)
{
	if(!p)
	return INF;
	if(tr[tr[p].l].size>=rank)
	return get_key_by_rank(tr[p].l,rank);
	if(tr[tr[p].l].size+tr[p].cnt>=rank)
	return tr[p].key;
	return get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
}
int get_prev(int p,int key)
{
	if(!p)
	return -INF;
	if(tr[p].key>=key)
	return get_prev(tr[p].l,key); 
	return max(tr[p].key,get_prev(tr[p].r,key));
}
int get_next(int p,int key)
{
	if(!p)
	return INF;
	if(tr[p].key<=key)
	return get_next(tr[p].r,key);
	return min(tr[p].key,get_next(tr[p].l,key));
}
int main()
{
	build();
	scanf("%d",&n);
	while(n--)
	{
		int op,x;
		scanf("%d%d",&op,&x);
		if(op==1)
		insert(root,x);
		else if(op==2)
		remove(root,x);
		else if(op==3)
		printf("%d\n",get_rank_by_key(root,x)-1);//哨兵 
		else if(op==4)
		printf("%d\n",get_key_by_rank(root,x+1));//哨兵 
		else if(op==5)
		printf("%d\n",get_prev(root,x));
		else
		printf("%d\n",get_next(root,x));
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值