平衡树--treap

treap==tree+heap,二叉搜索树(BST)+大根堆

1.Binary search tree (BST) 二叉搜索树

满足条件:

 

 一般看BST的中序遍历:1 2 3 4 5 6 7 8 9

一般的操作:

 前面四个操作set就可以自己实现,其中6跟7的数可能在树中不存在,而3跟4找的数一定存在

heap的操作就是让BST尽量随机,使得期望值最高

涉及两个操作左旋跟右旋:

交换子节点跟父节点,操作完后的中序遍历不变 ,任然等于AyBxC

目录

1.普通平衡树

2.营业额统计


1.普通平衡树

253. 普通平衡树 - AcWing题库

 #include<bits/stdc++.h>
using namespace std;
const int N=100010,INF=1e8;
int n;
struct Node
{
    int l,r;//左右子节点
    int key,val;//key存值,val存heap的随机值值
    int cnt,size;//cnt记录key的个数,size表示子树的总共的树包括自己
}tr[N];
int root,idx;//相当于链表一样,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].size=tr[idx].cnt=1;
    return idx;
}
void built()//建立链表
{
    get_node(-INF),get_node(INF);//建立一个负无穷与正无穷,防止有边界问题
    root=1,tr[1].r=2;//根节点为1,根节点的右节点是2
   pushup(root);//更新一下根节点
}
void zig(int &p)//右旋,按照图进行操作
{
    int q=tr[p].l;//先q指针指向左节点这个位置
    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;//先q指针指向右节点这个位置
    tr[p].r=tr[q].l,tr[q].l=p,p=q;//进行更换
    pushup(tr[p].l),pushup(p););//更新一下节点信息
}
void insert(int &p,int key)//插入一个值
{
    if(!p) p=get_node(key);//假如不存在,则添加一个节点
    else if(tr[p].key==key) tr[p].cnt++;//假如有相同的,则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)//删除一个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;//假如是跟节点,直接为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;//假如找到了,则等于左边+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)//找到严格小于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)//找到严格大于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()
{
    built();
    scanf("%d",&n);
    while(n--)
    {
        int opt,x;
        scanf("%d%d",&opt,&x);
        if(opt==1) insert(root,x);//
        else if(opt==2) remove(root,x);
        else if(opt==3) printf("%d\n",get_rank_by_key(root,x)-1);//因为有个负无穷的节点则节点位置要-1
        else if(opt==4) printf("%d\n",get_key_by_rank(root,x+1));//因为有个负无穷的节点则节点位置要+1
        else if(opt==5) printf("%d\n",get_prev(root,x));
        else printf("%d\n",get_next(root,x));
    }
    return 0;
}

2.营业额统计

265. 营业额统计 - AcWing题库

相似的题:邻值查找

136. 邻值查找 - AcWing题库

题意:在前i个数找到与ai最近的数

则转换为1.找到大于等于ai的最小数2.找到小于等于ai的最大数,然后要最接近的数即可,也即平衡树的两个基本操作找前驱跟后继,这题用set也可以直接过

lower_bound:大于某个数的最小数

upper_bound:找到大于等于某个数的最小数,然后在--就是这个数的前驱,就找到小于等于某个数的最大数了

 #include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=33000,INF=1e7;
int n;
int root,idx;//相当于链表的位置
struct Node
{
    int l,r;
    int key,val;
}tr[N];
int get_node(int key)//插入一个节点
{
    tr[++idx].key=key;
    tr[idx].val=rand();
    return idx;
}
void built()//建立两个正无穷与负无穷节点
{
    get_node(-INF),get_node(INF);
    root=1,tr[1].r=2;
}
void zig(int &p)//右旋
{
    int q=tr[p].l;
    tr[p].l=tr[q].r,tr[q].r=p,p=q;
}
void zag(int &p)//左旋
{
    int q=tr[p].r;
    tr[p].r=tr[q].l,tr[q].l=p,p=q;
}
void insert(int &p,int key)//插入一个key的节点
{
    if(!p) p=get_node(key);//假如不存在这个节点,开辟一个
    else if(tr[p].key==key) return;//假如已经存在了
    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);//假如值变大了,则左旋一下
    }
}
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_next(int p,int key)//获取大于等于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()
{
   ll res=0;
   built();//建立空结点
   scanf("%d",&n);
   for(int i=1;i<=n;i++)
   {
       int x;
       scanf("%d",&x);
       if(i==1) res+=x;
       else res+=min(x-get_prev(root,x),get_next(root,x)-x);//取最小
       insert(root,x);
   }
   printf("%lld\n",res);
    return 0;
}

stl中set的做法

#include<bits/stdc++.h>
using namespace std;
const int N=33000,INF=1e7;
set<int>s;
set<int>::iterator k,a;//set的两个迭代器
int n;
int main()
{
    int ans=0;
    s.insert(-INF),s.insert(INF);//插入两个正无穷和负无穷节点
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        if(i==1) ans+=x;
        else
        {
           k=s.lower_bound(x);//获取大于等于x的第一个数
           a=k;
           a--;//a是小于等于x的第一个数
           ans+=min(x-*a,*k-x);//取两边最小
        }
        s.insert(x);
    }
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值