[学习笔记] fhq Treap 平衡树

news2024/12/23 10:28:04

fhq Treap 也叫无旋Treap (好像?我也不知道)

反正我带旋 Treap 是不会滴,其他的平衡树也不会(但是会平板电视)

fhq Treap 好写,码量小,缺点是常数比较大

定义

二叉搜索树

二叉搜索树是一种二叉树的树形数据结构,其定义如下:

  1. 空树是二叉搜索树。

  2. 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。

  3. 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。

  4. 二叉搜索树的左右子树均为二叉搜索树。

至于二叉搜索树怎么写我也不知道

但是由于可以构造数据使得二叉搜索树退化成一条链所以平衡树就应运而生了

平衡树是通过左旋和右旋各种奇怪的操作使左子树和右子树的高度最多相差 1 的二叉搜索树

Treap 就是一种弱平衡的平衡树

Treap 顾名思义就是 Tree + Heap 是加入了堆来防止二叉搜索树退化(说白了就是随机化

其中,二叉搜索树的性质是:

左子节点的值( val \textit{val} val)比父节点大
右子节点的值( val \textit{val} val)比父节点小(当然这也是可以反过来的)

堆的性质是:

子节点值( key \textit{key} key)比父节点大或小(取决于是小根堆还是大根堆)
不难看出,如果用的是同一个值,那这两种数据结构的性质是矛盾的,所以我们再在搜索树的基础上,引入一个给堆的值 key \textit{key} key。对于 val \textit{val} val 值,我们维护搜索树的性质,对于 key \textit{key} key 值,我们维护堆的性质。其中 key \textit{key} key 这个值是随机给出的。

搬个 OI-Wiki 的图片
在这里插入图片描述
Treap 的核心操作是左旋和右旋 而 fhq Treap 则是不带旋的 Treap,很多情况下会一些操作好写很多

fhq Treap

fhq Treap 的核心操作是分裂 ( s p l i t split split) 和 合并 ( m e r g e merge merge)

节点信息

一个节点中的信息应该很好想罢,值 v a l val val ,键值 k e y key key, 左儿子 l l l ,右儿子 r r r 以及子树大小 s i z siz siz

struct treap{
	int val,key,siz,l,r;
}fhq[N << 1];

建立新节点

建立一个新节点其实就是把一个节点初始化掉

int new_treap(int val){
	fhq[++cnt].val = val;
	fhq[cnt].key = rand();
	fhq[cnt].siz = 1;
	return cnt;
}

很好理解对吧

更新父节点信息

其实就和线段树的 p u s h _ u p push \_ up push_up操作是一样的

void push_up(int pos){
	fhq[pos].siz = fhq[fhq[pos].l].siz + fhq[fhq[pos].r].siz + 1;
}

分裂

分裂操作有两种,一种是按值分裂,把所有值小于等于 v a l val val 的分裂成一颗树,把值 大于 v a l val val分裂成一颗树;一种是按大小分裂

一般把 fhq Treap 当正常平衡树使用的时候都是用按值分裂

直接看代码理解罢

void split(int pos, int val, int& x, int& y){//因为是一棵树分裂成两颗树,返回pair会比较麻烦,所以直接引用一下 
	if(!pos) {//到底了不能分裂 
		x = y = 0;
		return;
	}
	if(fhq[pos].val <= val){//小于val的分裂到x树去 
		x = pos;
		split(fhq[pos].r, val, fhq[pos].r, y);//右子树继续分裂 
	}else{
		y = pos;//大于val到y树去 
		split(fhq[pos].l,val,x,fhq[pos].l);//左子树继续分裂 
	}
	push_up(pos);
}

合并

合并当然就是分裂反过来噜~~

int merge(int x, int y){//注意这里x树是分裂出来小的那个树 
	if(!x || !y) return x+y;//如果x为空就返回y,如果y没有就返回x,如果两个都没有就返回0 
	if(fhq[x].key > fhq[y].key){//x的键值大,y并到x的右子树去 
		fhq[x].r = merge(fhq[x].r, y);
		push_up(x);
		return x;
	}else{
		fhq[y].l = merge(x,fhq[y].l);//y的键值大,x并到y的左子树去 
		push_up(y);
		return y;
	}
}

平衡树常规操作

前面两个操作就很短对吧,后面还要短

插入一个值 val

插入分两步

  1. 把树按 v a l val val 分裂成两棵树
  2. v a l val val 和小于等于 v a l val val 那棵树合并,再合并回去
void insert(int val){
	int x,y;
	split(root,val,x,y);
	root = merge(merge(x,new_treap(val)),y);
}

删除一个值 val

删除分为三步

  1. v a l val val 分裂成两棵树
  2. 把小于等于 v a l val val 的那棵树再按 v a l − 1 val-1 val1 分裂成两棵树(这样我们就得到了全是 v a l val val 的一棵树)
  3. 把全是 v a l val val 的那棵树的根节点删掉(合并左子树和右子树就可以了)
  4. 把剩下的树按顺序合并回去
void del(int val){
   int x,y,z;
   split(root,val,x,z);
   split(x,val-1,x,y);
   y = merge(fhq[y].l,fhq[y].r);
   root = merge(merge(x,y),z);
}

查询 val 的排名

这个也很简单啊,就两步

  1. 把树按 v a l − 1 val-1 val1 分裂
  2. 小的那棵树的 s i z + 1 siz+1 siz+1 就可以了
  3. 记得合并回去
void get_rank(int val){
	int x,y;
	split(root,val-1,x,y);
	cout << fhq[x].siz+1 << endl;
	root = merge(x,y);
}

查询排名为 rank 的值

这个稍微麻烦一点

void get_num(int rank){
	int now = root;
	while(now){
		if(fhq[fhq[now].l].siz+1 == rank) break;
		else if(fhq[fhq[now].l].siz >= rank) now = fhq[now].l;
		else{
			rank -= fhq[fhq[now].l].siz+1;
			now = fhq[now].r;
		}
	}
	cout << fhq[now].val << endl;
}

查找前驱/后继

void pre(int val){
	int x,y;
	split(root,val-1,x,y);
	int now = x;
	while(fhq[now].r) now = fhq[now].r;
	cout << fhq[now].val << endl;
	root = merge(x,y);
}
void nxt(int val){
	int x,y;
	split(root,val,x,y);
	int now = y;
	while(fhq[now].l){
		now = fhq[now].l;
	}
	cout << fhq[now].val << endl;
	root = merge(x,y);
}

板子传送门

Code

#include <bits/stdc++.h>
const int N = 1e5+10;
using namespace std;
struct treap{
	int val,key,siz,l,r;
}fhq[N << 1];
int cnt = 0,root;
int new_treap(int val){
	fhq[++cnt].val = val;
	fhq[cnt].key = rand();
	fhq[cnt].siz = 1;
	return cnt;
}
void push_up(int pos){
	fhq[pos].siz = fhq[fhq[pos].l].siz + fhq[fhq[pos].r].siz + 1;
}
void split(int pos, int val, int& x, int& y){//因为是一棵树分裂成两颗树,返回pair会比较麻烦,所以直接引用一下 
	if(!pos) {//到底了不能分裂 
		x = y = 0;
		return;
	}
	if(fhq[pos].val <= val){//小于val的分裂到x树去 
		x = pos;
		split(fhq[pos].r, val, fhq[pos].r, y);//右子树继续分裂 
	}else{
		y = pos;//大于val到y树去 
		split(fhq[pos].l,val,x,fhq[pos].l);//左子树继续分裂 
	}
	push_up(pos);
}
int merge(int x, int y){//注意这里x树是分裂出来小的那个树 
	if(!x || !y) return x+y;//如果x为空就返回y,如果y没有就返回x,如果两个都没有就返回0 
	if(fhq[x].key > fhq[y].key){//x的键值大,y并到x的右子树去 
		fhq[x].r = merge(fhq[x].r, y);
		push_up(x);
		return x;
	}else{
		fhq[y].l = merge(x,fhq[y].l);//y的键值大,x并到y的左子树去 
		push_up(y);
		return y;
	}
}
void insert(int val){
	int x,y;
	split(root,val,x,y);
	root = merge(merge(x,new_treap(val)),y);
}
void del(int val){
	int x,y,z;
	split(root,val,x,z);
	split(x,val-1,x,y);
	y = merge(fhq[y].l,fhq[y].r);
	root = merge(merge(x,y),z);
}
void get_rank(int val){
	int x,y;
	split(root,val-1,x,y);
	cout << fhq[x].siz+1 << endl;
	root = merge(x,y);
}
void get_num(int rank){
	int now = root;
	while(now){
		if(fhq[fhq[now].l].siz+1 == rank) break;
		else if(fhq[fhq[now].l].siz >= rank) now = fhq[now].l;
		else{
			rank -= fhq[fhq[now].l].siz+1;
			now = fhq[now].r;
		}
	}
	cout << fhq[now].val << endl;
}
void pre(int val){
	int x,y;
	split(root,val-1,x,y);
	int now = x;
	while(fhq[now].r) now = fhq[now].r;
	cout << fhq[now].val << endl;
	root = merge(x,y);
}
void nxt(int val){
	int x,y;
	split(root,val,x,y);
	int now = y;
	while(fhq[now].l){
		now = fhq[now].l;
	}
	cout << fhq[now].val << endl;
	root = merge(x,y);
}
int n;

int main(){
	cin >> n;
	while(n--){
		int op,x;
		cin >> op >> x;
		if(op == 1){
			insert(x);
		}
		if(op == 2){
			del(x);
		}
		if(op == 3){
			get_rank(x);
		}
		if(op == 4){
			get_num(x);
		}
		if(op == 5){
			pre(x);
		}
		if(op == 6){
			nxt(x);
		}
	}	
	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么说模电难学?因为它至少是这27个基础知识的排列组合!

1、基尔1、基尔霍夫定理的内容是什么&#xff1f; 基尔霍夫电流定律&#xff1a;在电路任一节点&#xff0c;流入、流出该节点电流的代数和为零。 基尔霍夫电压定律&#xff1a;在电路中的任一闭合电路&#xff0c;电压的代数和为零。 2、戴维南定理 一个含独立源、线性电阻…

在抖音开店卖货的流程是什么?最全解答如下,建议新手认真看完!

我是王路飞。 同样是在抖音卖货&#xff0c;为何如今大多数人都是选择在抖音开店&#xff0c;而不再是选择做账号、开直播了呢&#xff1f; 原因很简单&#xff0c;因为门槛和变现方式。 相比短视频和直播带货的起号、变现难度越来越大&#xff0c;低门槛的抖音小店显然更适…

Origin热图的做法

1.数据准备 2.绘制-选择带标签热图 3.图表调整 右边标签- 下方标签-复制格式&#xff0c;再到左边或者右边选择 粘贴所有 图中的标签及颜色 直接双击在属性框更改 主框的其他特征在属性框选择 色阶的控制 直接选择色阶的属性框

Endnote中查看一个文献的分组的具体方法——以Endnote X8为例

Endnote中查看一个文献的分组的具体方法——以Endnote X8为例 一、问题 当Endnote中使用分类方法对文献进行分组管理后&#xff0c;有时需要重新调整该文献的分组&#xff0c;则需要找到这个文献在哪个分组中。本文阐述怎样寻找一个文献的分组的位置信息。 二、解决方法 1.选…

Spooling的原理

脱机技术 程序猿先用纸带机把自己的程序数据输入到磁带中&#xff0c;这个输入的过程是由一台专门的外围控制机实现的。之后CPU直接从快速的磁带中读取想要的这些输入数据。输出也类似。 假脱机技术&#xff08;Spooling技术&#xff09; 即用软件的方式来模拟脱机技术。要…

Kubernetes技术--k8s核心技术Controller控制器

1.Controller概述 Controller是在集群上管理和运行容器的对象。是一个实际存在的对象。 2.pod和Controller之间的关系 pod通过controller实现应用的运维,包括伸缩、滚动升级等操作。 这里pod和controller通过label标签来建立关系。如下所示: 3.Deployment控制器应用场景 -1:…

RabbitMQ工作模式-发布订阅模式

Publish/Subscribe&#xff08;发布订阅模式&#xff09; 官方文档&#xff1a; https://www.rabbitmq.com/tutorials/tutorial-three-python.html 使用fanout类型类型的交换器&#xff0c;routingKey忽略。每个消费者定义生成一个队列关绑定到同一个Exchange&#xff0c;每个…

win10底部任务栏开机后长时间未响应的解决办法

https://blog.csdn.net/hj960511/article/details/128746025?share_tokenAA088876-4477-44B8-B978-5C7C7726D552&tt_fromcopy_link&utm_sourcecopy_link&utm_mediumtoutiao_ios&utm_campaignclient_share win10底部任务栏开机后长时间未响应的解决办法-CSDN博…

亚马逊云科技re:Inforce大会:为企业提供端到端的安全防护能力

2023年&#xff0c;生成式AI带来了无数的创新&#xff0c;并将会在行业应用中产生更多的新能力、新场景。与此同时&#xff0c;关于生成式AI的风险管控成为各方关注焦点&#xff0c;数据隐私、合规保护、防欺诈等&#xff0c;已成为生成式AI时代的安全合规的新话题。 随着云上业…

(一)SpringBoot 整合WebSocket 前端 uniapp 访问

第一次使用WebSocket&#xff0c;所以最需要一个及其简单的例子&#xff0c;跑通之后&#xff0c;增加自己对该技术的理解。&#xff08;技术基础介绍就免掉了&#xff0c;后面再补&#xff09; 案例逻辑&#xff1a;目前只有一个用户&#xff0c;而且是一个用户给服务器发送数…

linux系统中详解u-boot之网络移植与调试

​今天给大家讲一讲如何完善u-boot网络部分的移植和调试。 一、前章回顾 上一章&#xff0c;已经讲过如何讲uboot.2022.10版本移植到我们自己的imx6ull开发板上&#xff0c;但是最后编译下载后网络部分未能正确识别&#xff0c;今天我们就来讲一讲网络部分的调试。 上一篇ub…

静力触探数据智能预处理(2)

静力触探数据智能预处理&#xff08;2&#xff09; 前言 数据处理方式已由手工1.0、计算机辅助2.0到人工智能3.0的趋势发展。现场采集的静力触探数据通常是由仪器厂家开发的数据采集软件保存&#xff0c;将原始数据导入Excel中&#xff0c;数据格式需要花费一定的时间整理&am…

Git管理本地代码

一、Git配置 当安装完 Git 应该做的第一件事就是设置你的用户名称与邮件地址。 这样做很重要&#xff0c;因为每一个 Git 的提交都会使用这些信息&#xff0c;并且它会写入到你的每一次提交中&#xff0c;不可更改 – global全局配置 通过 --global 选项可以设置全局配置信息 …

第6篇:ESP32连接无源喇叭播放音乐《涛声依旧》

第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 第3篇:vscode搭建esp32 arduino开发环境 第4篇:vscodeplatformio搭建esp32 arduino开发环境 第5篇:doit_esp32_devkit_v1使用pmw呼吸灯实验 D5连接喇叭正极&#xff0c;GND连接喇叭负…

推荐一本AI+医疗书:《机器学习和深度学习基础以及医学应用》,附21篇精选综述

当代医学仍然存在许多亟待解决的问题&#xff0c;比如日益增加的成本、医疗服务水平的下降...但近几年AI技术的发展却给医疗领域带来了革命性的变化&#xff0c;因此AI医疗迅速兴起。 从目前已知的成果来看&#xff0c;人工智能在医学领域的应用已经相当广泛&#xff0c;智能诊…

【pyqt5界面化工具开发-14】初始牛刀-登录工具

目录 0x00 前言&#xff1a; 一、准备好ui的加载 二、获取对应的触发事件 三、触发事件绑定 三、输入内容的调用 三、完善登录逻辑 0x00 前言&#xff1a; 在逻辑代码的处理添加数据包的请求&#xff0c;返回数据包的判断&#xff0c;就可以完整实现登录检测的一个界面化…

zookeeper介绍、zookeeper的安装与配置

1、zookeeper介绍 1.1 官网说明 官方地址&#xff1a;http://zookeeper.apache.org/ 它是拿来管理 Hadoop、Hive、Pig的管理员&#xff0c; Apache Hbase和Apache Solr以及阿里的Dubbo等项目中都采用到了Zookeeper。 一句话&#xff1a;ZooKeeper是一个分布式协调技术、高性…

基于Kohonen网络的聚类算法

1.案例背景 1.1 Kohonen网络 Kohonen网络是自组织竞争型神经网络的一种,该网络为无监督学习网络,能够识别环境特征并自动聚类。Kohonen神经网络是芬兰赫尔辛基大学教授Teuvo Kohonen 提出的,该网络通过自组织特征映射调整网络权值,使神经网络收敛于一种表示形态。在这一形态中…

OLAP学习

OLAP又叫联机分析处理&#xff0c;联机分析处理(OLAP)的概念最早是由关系数据库之父E.F.Codd于1993年提出的。 当今的数据处理大致可以分成两大类&#xff1a;联机事务处理OLTP&#xff08;on-linetransactionprocessing&#xff09;、联机分析处理OLAP&#xff08;On-LineAna…

外观模式:简化复杂子系统的访问与使用

文章目录 1. 简介2. 外观模式的基本结构3. 外观模式的实现步骤4. 外观模式的应用与实例4.1 图形界面库的外观模式应用4.2 文件压缩与解压缩的外观模式应用4.3 订单处理系统的外观模式应用 5. 外观模式的优缺点5.1 优点5.2 缺点 6. 总结 1. 简介 外观模式是一种结构型设计模式&…