平衡树--treap

简介: 平衡树--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.普通平衡树

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;
}
相关文章
N..
|
开发框架 前端开发 JavaScript
Bootstrap轮播图
Bootstrap轮播图
N..
261 1
|
算法 安全 数据安全/隐私保护
介绍一下移动应用中的数据加密技术。
移动应用数据加密保护隐私,包括对称加密(速度快但密钥管理难)、非对称加密(公钥私钥确保安全如RSA、ECC)、哈希函数(固定长度输出验证信息)和数字签名(公钥验证来源与完整性)。选择合适的加密算法对安全性至关重要,兼顾性能以不影响用户体验。加密技术确保信息的机密性、真实性和完整性,增强用户信任。开发者应熟练掌握这些工具。
422 0
|
开发工具 C语言 git
Vcpkg 的安装与使用
Windows 下 Vcpkg 的安装与使用
1772 0
Vcpkg 的安装与使用
|
7月前
|
数据采集 安全 数据挖掘
淘宝天猫宝贝详情页面商品评论采集接口全解析
淘宝天猫商品评论采集接口为电商数据挖掘提供了重要工具。通过分析海量评论,消费者可获取购买决策参考,商家能优化产品与服务,市场研究者则能洞察行业趋势与竞品表现。该接口支持Python请求,助力开发者构建智能分析应用,推动电商生态中各方价值提升。使用时需遵守平台规则,确保数据安全与合法利用。
241 15
|
存储 自然语言处理 API
通义万相AIGC技术Web服务体验评测
随着人工智能技术的不断进步,图像生成技术已成为创意产业的一大助力。通义万相AIGC技术,作为阿里云推出的一项先进技术,旨在通过文本到图像、涂鸦转换、人像风格重塑及人物写真创建等功能,加速艺术家和设计师的创作流程。本文将详细评测这一技术的实际应用体验。
492 4
|
11月前
|
API Docker 微服务
Ocelot集成Consul实现api网关与服务发现
本文介绍了如何在.NET微服务架构中集成API网关Ocelot和Consul服务发现。首先通过Docker安装并配置Consul,接着在GoodApi项目中实现服务的自动注册与注销,并配置健康检查。然后,通过修改Ocelot的配置文件`ocelot.json`和`Program.cs`,实现基于Consul的服务发现,确保API请求能够正确路由到后端服务。最后,解决了服务解析时可能出现的问题,确保服务的IP地址而非节点名称被正确解析。
254 0
Ocelot集成Consul实现api网关与服务发现
|
存储 数据库
缺陷知识库
缺陷知识库
282 0
|
存储 数据库 数据安全/隐私保护
数据库原理第五章课后题答案(第四版)
数据库原理第五章课后题答案(第四版)
240 0
|
存储 缓存 安全
基于GitHub/七牛云 + PicGo 搭建属于Typora的图床
基于GitHub/七牛云 + PicGo 搭建属于Typora的图床
基于GitHub/七牛云 + PicGo 搭建属于Typora的图床