平衡树——treap

treap实际上就是tree(BST,二叉搜索树)+heap(堆)

我们维护一个二叉树来储存值,但是为了避免二叉树由于值太特殊变成链式结构,我们对于每个点加入一个val值,这个是随机值,我们通过这个随机值来维护一个大根堆(只与val有关的大根堆),进而使得我们维护能够用一个比较对称的二叉树来维护所有数据,可以类比AVL树来思考。

那么我们为什么要用treap,或者换句话说treap可以进行哪些操作呢?

1.插入

2.删除

3.找前驱和后继(小于某个数最大的数和大于某个数最小的数,BST中不同节点数不同)

4.找最大/最小

5.求某个值的排名

6.求排名是rank的数是哪个

7.比某个数小的最大值(数可能不在我们维护的区间中)

8.比某个数大的最小值(同上)

对于这些操作,显然前几个是可以用set实现的,但是求某个值的排名和求排名是k的数是哪个,这两个操作显然用set没办法很好的实现。所以我们必须要使用Treap。能用set的一定可以用Treap,能用Treap的不一定能用set。

那么我们就要来看如何实现Treap,Treap是基于BST的,那么我们先来回顾一下BST,BST就是维护一棵二叉树,对于这个二叉树而言,它左子节点的值一定小于它,右子节点的值一定大于它。

我们一般是以第一个数为根节点的,但是如果要维护的区间是单调的,那么二叉树就会退化成链式结构,很影响时间复杂度。所以为了避免二叉树退化成链式结构,我们在每个点上增加一个随机值变量,然后根据这个随机值维护一个大根堆,也即父节点的值一定大于左右子节点。然后如果不满足的话,那么就把左右子节点中的大的点通过旋转操作放到根节点上去,旋转操作如下:

可以发现即使我们进行了旋转操作,维护的仍然是一个二叉树,不会对我们在二叉树上的操作产生影响。与此同时,这也给了我们一个删除中间节点的思路,我们可以把中间节点换到叶子节点上进行删除。

那么我们下面来看代码层面的实现:

首先是对每个节点的定义,最基本的需要一下几个值(当然由于题目不同,可能还要加一些别的变量):

struct node{
    int l,r;//l,r表示左右子节点的下标,我们这里用下标表示指针
    int key,val;//key表示我们实际存的值,val存的是我们随机赋的值,用以维护堆
}

 然后我们来看有哪些基本操作:

新增节点:

int get_node()
{
    tr[++idx].key=key;
    tr[idx].val=rand();
    return idx;
}

左旋:

右到父,父到左,右左到左右。先拔再转。

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);
}

 通过前三个赋值,我们已经实现了子节点的交换,相对于原来的p的父节点的更新我们该怎么实现了,显然我们没有存父节点的信息, 不能从子节点访问到父节点,但是我们肯定是在递归中实现的,我们只要在递归的时候传入tr[u].l的引用,那么在这一层进行修改的时候就可以实现对tr[u].l的修改。

右旋:

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);
}

这里的pushup就是用子节点更新父节点的一些信息,和线段树中的pushup差不多。

初始化:

初始化的时候,我们设置了两个哨兵,它们的值分别赋值为正无穷和负无穷,先令root为负无穷对应的点1,它的右子为正无穷对应的点2.然后根据它们的val,判断是否需要左旋。

void build()
{
    get_node(-INF),get_node(INF);
    root=1,tr[root].r=2;
    pushup(root);
    if(tr[1].val<tr[2].val) zag(root);//维护大根堆
}

插入

和二叉树的插入操作一样,注意判断是否需要旋转操作即可。

void insert(int &p,int key)
{
    if(!p) get_node(key);
    else if(tr[p].key<key)
    {   
        insert(tr[p].r,key);
        if(tr[p].val<tr[tr[p].r].val) zig(p);
    }
    else 
    {   
        insert(tr[p].l,key);
        if(tr[p].val<tr[tr[p].l].val) zag(p);
    }
}

删除

删除要分三种情况,一种是要删除的数不存在,那么最后搜到的位置就是0,那么直接返回即可;一种是要删除的数在叶子节点上,那么我们直接把这个叶子节点的下标修改成0即可,因为我们在上一层传入的是它父节点的tr[u].l或者tr[u].r的引用,所以这里直接赋成0就相当于将它父节点的左指针或者右指针的指向修改成空;最后一种最麻烦,就是要删除的数不在叶子节点上,那么就需要通过旋转将它换到叶子节点上,然后再用第二种方法执行删除。

void remove(int &p,int key)
{
    if(!p) return;
    if(tr[p].l||tr[p.r])
    {
        if(!tr[p].r||tr[tr[p].l].val>tr[tr[p].r].val)
        {
            zig(p);//p和原p的父节点的某个子节点都被修改成了原p的左子节点
            remove(tr[p].r,key);
        }
        else
        {
            zag(p);
            remove(tr[p].l,key);
        }
    }
    else p=0;
}

找严格小于key的数中最大的数

int get_prev(int p,int key)
{
    if(!p) return -INF;//没有小于key的数
    if(tr[p].key>=key) return get_prev(tr[p].l,key);
    return max(tr[p].key,get_prev(tr[p].r,key));
}

找到严格大于key的最小数

int get_next(int p,int key)
{
    if(!p) return INF;
    if(p.key<=key) return get_next(tr[p].r,key);
    return min(tr[p].key, get_next(tr[p].l,key) );
}

找最大:

int get_max(int p)
{
    if(!p) return -INF;
    return max(tr[p].key,get_max(tr[p].r));
}

找最小:

int get_min(int p)
{
    if(!p) return INF;
    return min(tr[p].key,get_max(tr[p].l));
}

求某个值的排名

求排名的时候为了方便,我们引入了以某个点为根节点的子树的大小size,如果有重复元素,我们还可以引入一个cnt变量,表示值为当前节点的点有多少个。

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()+get_rank_by_key(tr[p].r,key);
}

求排名是rank的数是哪个

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[p].size+tr[p].cnt>=key) return tr[p].key;//cnt表示的是当前数有多少个
    return  get_key_by_rank(tr[p].r,rank-tr[tr[p].l].size-tr[p].cnt);
}

差不多就是这些操作,剩下的我们根据具体的题目再来分析。

253. 普通平衡树(253. 普通平衡树 - AcWing题库)

这个就是一道比较裸的题目,这里要注意我们需要在原来节点定义的基础上引入size和cnt,因为这里有根据排名求数和根据数求排名两个操作。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
struct node{
    int l,r;
    int key,val;
    int size,cnt;
}tr[100010];
int root,idx;
int get_node(int key)
{
   tr[++idx].key=key;
   tr[idx].val=rand();
   tr[idx].size=tr[idx].cnt=1;
   return idx;
}
void pushup(int p)
{
    tr[p].size=tr[tr[p].l].size+tr[p].cnt+tr[tr[p].r].size;
}
void left(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 right(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 build()
{
    get_node(-inf),get_node(inf);
    root=1,tr[1].r=2;
    pushup(root);
    if(tr[1].val<tr[2].val) left(root);
}
void insert(int &p,int key)
{
    if(!p) p=get_node(key);//这里赋值了才算被插进去,因为p传入的是引用
    else if(tr[p].key==key) tr[p].cnt++;
    else if(tr[p].key>key) 
    {
        insert(tr[p].l,key);
        if(tr[p].val<tr[tr[p].l].val) right(p);
    }
    else 
    {
        insert(tr[p].r,key);
        if(tr[p].val<tr[tr[p].r].val) left(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)
                {
                    right(p);
                    remove(tr[p].r,key);
                }
                else 
                {
                    left(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;
    else if(tr[p].key>key) return get_rank_by_key(tr[p].l,key);
    else 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);
    else if(tr[tr[p].l].size+tr[p].cnt>=rank) return tr[p].key;
    else get_key_by_rank(tr[p].r,rank-tr[p].cnt-tr[tr[p].l].size);
}
int get_prev(int p,int key)
{
    if(!p) return -inf;
    if(tr[p].key>=key) return get_prev(tr[p].l,key);
    else 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);
    else return min(tr[p].key,get_next(tr[p].l,key));
}
int main()
{
    build();
    int n;
    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) cout<<get_rank_by_key(root,x)-1<<endl;//因为有哨兵
        else if(op==4) cout<<get_key_by_rank(root,x+1)<<endl;
        else if(op==5) cout<<get_prev(root,x)<<endl;
        else cout<<get_next(root,x)<<endl;
    }
}

265. 营业额统计(265. 营业额统计 - AcWing题库)

思路:这题对于每个数要找到在它插入前距离它最近的数,那么实际上就是在每个数插入前找它的前驱(最大的最小值)和后继(最小的最大值),然后取离它更近的那个值。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
struct node{
    int l,r;
    int key,val;
}tr[100010];
int root,idx;
int get_node(int key)
{
    tr[++idx].key=key;
    tr[idx].val=rand();
    return idx;
}
void left(int &p)
{
    int q=tr[p].r;
    tr[p].r=tr[q].l,tr[q].l=p,p=q;
}
void right(int &p)
{
    int q=tr[p].l;
    tr[p].l=tr[q].r,tr[q].r=p,p=q;
}
void build()
{
    get_node(-inf),get_node(inf);
    root=1,tr[1].r=2;
    if(tr[1].val<tr[2].val) left(root);
}
void insert(int &p,int 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) right(p);
    }
    else 
    {
        insert(tr[p].r,key);
        if(tr[tr[p].r].val>tr[p].val) left(p);
    }
}
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();
    int n;
    scanf("%d",&n);
    long long ans=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        if(i==1) ans+=x; 
        else ans+=min(x-get_prev(root,x),get_next(root,x)-x);
        insert(root,x);
    }
    printf("%lld",ans);
}

在插入的时候,一定不要忘记判断是否需要左右旋。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/461843.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JDK8和JDK11在Ubuntu18上切换(解决nvvp启动报错)

本文主要介绍JDK8和JDK11在Ubuntu18上切换&#xff0c;以供读者能够理解该技术的定义、原理、应用。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;计算机杂记 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人…

Android Studio实现内容丰富的安卓宠物用品商店管理系统

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号128 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.系统公告 3.宠物社区&#xff08;可发布宠物帖子&#…

ts的interface和type区别

1. 场景 interface 是用来描述对象类型的结构&#xff0c;可以定义对象的属性名和属性值的类型&#xff0c;也可以定义函数类型。interface User {name: string;age: number;sayHello(): void; } const user: User {name: "",age: 2,sayHello() {...} }可以用这个U…

Linux 自动备份 mysql 脚本

这个脚本会将数据库备份为一个SQL文件&#xff0c;并将其保存在指定的目录中。 #!/bin/bash# MySQL配置 DB_USER"your_mysql_username" DB_PASS"your_mysql_password" DB_NAME"your_database_name" DB_HOST"localhost"# 备份目录 BAC…

力扣hot100:416.分割等和子集(组合/动态规划/STL问题)

组合数问题 我们思考一下&#xff0c;如果要把数组分割成两个子集&#xff0c;并且两个子集的元素和相等&#xff0c;是否等价于在数组中寻找若干个数使之和等于所有数的一半&#xff1f;是的&#xff01; 因此我们可以想到&#xff0c;两种方式&#xff1a; ①回溯的方式找到t…

springboot275毕业就业信息管理系统的设计与实现

毕业就业信息管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装毕业就业信息管理系统软件…

安装jupyter报错:404 GET /static/notebook/4131.bundle.js

1、报错安装过程 我直接是pip install jupyter 进行的安装&#xff0c;如下&#xff0c;安装的版本是7.1.2 2、报错结果 运行jupyternotebook后报错&#xff1a;404 GET /static/notebook/4131.bundle.js (3bea7012d1534d70a935c3c193d9308d127.0.0.1) 5.70ms refererht…

SMART PLC 卷径计算(圈数检测+膜厚叠加法)

1、卷径计算(膜厚叠加+数值积分器应用博途PLC SCL代码) https://rxxw-control.blog.csdn.net/article/details/136719982https://rxxw-control.blog.csdn.net/article/details/1367199822、膜厚叠加法 https://rxxw-control.blog.csdn.net/article/details/128600466

关于前端打包加部署

1.首先输入命令 npm build 2.打包完成进入xshell&#xff0c;输入命令 tar -zcvf "da 20240315 登录.tar.gz" * 这个命令是在Linux或类Unix系统上使用的tar命令&#xff0c;用于创建一个名为 "da 20240315 登录.tar.gz" 的归档文件&#xff0c;其中包含…

皂液器问卷调查

媳妇非要买这种皂液器&#xff0c;来问问友友们有用过的帮忙识别一下是否是真的好用&#xff1a;皂液器问卷调查 4个题

代码随想录算法训练营第41天 | 01背包问题(二维+一维) ,416. 分割等和子集

动态规划章节理论基础&#xff1a; https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 01背包理论基础 链接&#xff1a;https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%…

C++初阶:模板初阶

目录 1. 模板的引入2. 函数模板与类模板2.1 函数模板2.2 模板调用方式2.3 函数模板与普通函数的调用优先性2.4 类模板2.5 类模板的构造函数&#xff0c;类模板声明与定义分离 1. 模板的引入 我们来看下面这几个函数&#xff1a; void swap(int& left, int& right) {int…

vuepress-theme-vdoing博客搭建教程

搭建流程 前言 这是笔者搭建个人博客所经历的流程&#xff0c;特附上笔记 笔者个人博客地址&#xff1a;沉梦听雨的编程指南 一、主题介绍 本博客使用的主题为&#xff1a;vuepress-theme-vdoing&#xff0c;相关介绍和使用方法可以参考该主题的官方文档 官方文档快速上手…

java线程池基础

目录 一、线程池基础概念1.1 什么是线程池&#xff1f;1.2 为什么要用线程池&#xff1f;1.3 线程池的工作原理 二、java中的线程池2.1 线程池真正实现类 ThreadPoolExecutor 2.1.2 构造器2.1.3 参数2.1.3.1 workQueue2.1.3.2 threadFactory2.1.3.3 handler 2.2 线程池的使用2.…

超详细解析:在执行一条SQL语句期间发生了什么?

目录 前言MySQL的执行流程Server层连接器查询缓存词法分析器预处理优化器执行器 引擎层具体流程为什么需要redologredolog的组成redolog如何提高性能&#xff1f;redo log与binlog区别 总结 前言 我们学习MySQL时&#xff0c;首先第一个接触到的就是SQL语句了&#xff0c;那么…

MD5算法:密码学中的传奇

title: MD5算法&#xff1a;密码学中的传奇 date: 2024/3/15 20:08:07 updated: 2024/3/15 20:08:07 tags: MD5起源算法原理安全分析优缺点比较技术改进示例代码应用趋势 MD5算法起源&#xff1a; MD5&#xff08;Message Digest Algorithm 5&#xff09;算法是由MIT的计算机…

Linux之shell条件测试

华子目录 用途基本语法格式示例 文件测试参数示例 整数测试作用操作符示例~&#xff1a;检查左侧内容是否匹配右侧的正则表达式 案例分析逻辑操作符符号示例 命令分隔符&>&#xff1a;不管成功与否&#xff0c;都将信息写进黑洞中 用途 为了能够正确处理shell程序运行过…

django开发流式格式后的在nginx的部署的记录

关键记录. django上传代码要导出配置 pip freeze > requirements.txt 这个很关键。后面部署直接读取的 关键记录. django上传代码要导出配置 pip freeze > requirements.txt 这个很关键。后面部署直接读取的 关键记录. django上传代码要导出配置 pip freeze > require…

Python 基础语法:基本数据类型(字典)

为什么这个基本的数据类型被称作字典呢&#xff1f;这个是因为字典这种基本数据类型的一些行为和我们日常的查字典过程非常相似。 通过汉语字典查找汉字&#xff0c;首先需要确定这个汉字的首字母&#xff0c;然后再通过这个首字母找到我们所想要的汉字。这个过程其实就代表了…

Unity的AssetBundle资源运行内存管理的再次深入思考

大家好&#xff0c;我是阿赵。   这篇文章我想写了很久&#xff0c;是关于Unity项目使用AssetBundle加载资源时的内存管理的。这篇文章不会分享代码&#xff0c;只是分享思路&#xff0c;思路不一定正确&#xff0c;欢迎讨论。   对于Unity引擎的资源内存管理&#xff0c;我…
最新文章