平衡树 - splay

news2024/12/24 9:39:02

相比于之前的普通平衡树进行左旋右旋来比,splay的适用性更高,使用更广泛。

核心函数rotate、splay函数,其它的根据需要进行修改。

int n, m;
struct Node {
	int s[2], p, v, cnt; // 左右儿子、父节点、值、出现数量
	int size, flag; // 子树大小、懒标记
	void init(int _v, int _p) { // 初始化函数
		v = _v, p = _p;
		cnt = size =  1;
	}
} tr[N];
int root, idx;// 根节点、分配节点序号

void pushup(int u) { // 向上更新传递,与线段树一样 
	tr[u].size = tr[tr[u].s[0]].size + tr[tr[u].s[1]].size + tr[u].cnt;
}

void pushdown(int x) { // 向下传递更新 ,与线段树一样
	if(tr[x].flag) {
		swap(tr[x].s[0], tr[x].s[1]);
		tr[tr[x].s[0]].flag ^= 1;
		tr[tr[x].s[1]].flag ^= 1;
		tr[x].flag = 0;
	}
}

void rotate(int x) { // 核心函数 
	int y = tr[x].p, z = tr[y].p;
	int k = tr[y].s[1] == x;
	tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
	tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
	tr[x].s[k ^ 1] = y, tr[y].p = x;
	pushup(y), pushup(x);
}

void splay(int x, int k) { // 将x节点旋转到k节点下 
	while(tr[x].p != k) { // 
		int y = tr[x].p; // x节点的父节点 
		int z = tr[y].p; // x节点的父节点的父节点 
		if(z != k) // 向上旋转 
			if((tr[y].s[1] == x) != (tr[z].s[1] == y)) rotate(x); // 转一次x 
			else rotate(y); // 转一次y 
		rotate(x); // 转一次x 
	}
	if(!k) root = x; // 更新root节点 
}

void upper(int v) { // 将v值节点转到根节点 
	int u = root; // 根节点 
	while(tr[u].s[v > tr[u].v] && tr[u].v != v) // 存在则找到v值节点,不存在则找到v值节点的前驱或者后继节点 
		u = tr[u].s[v > tr[u].v]; // 向下寻找 
	splay(u, 0); // 将u节点旋转到跟节点 
}

int get_prev(int v) { // 获取v值的前驱节点 
	upper(v); // 将v值节点转到根节点 
	if(tr[root].v < v) return root; // 若是该值在树中不存在,根节点就是v的前驱或者后继节点 
	int u = tr[root].s[0]; // 前驱节点在左子树的最右边 
	while(tr[u].s[1]) u = tr[u].s[1]; // 找到最右边的一个节点 
	return u;
}

int get_next(int v) { // 获取某值的后继节点 
	upper(v); // 将v值节点转到根节点
	if(tr[root].v > v) return root; // 若是该值在树中不存在,根节点就是v的前驱或者后继节点 
	int u = tr[root].s[1]; // 后继节点在右子树的最左边 
	while(tr[u].s[0]) u = tr[u].s[0]; // 找到最左的节点,就是最小的节点 
	return u; // 返回节点 
}

int get_rank_by_key(int v) { // key值在当前树中的排名 
	upper(v); //  
	if(tr[root].v >= v)
		return tr[tr[root].s[0]].size + 1;
	return tr[tr[root].s[0]].size + tr[root].cnt + 1;
}

int get_key_by_rank(int k) { // 获取树中排名为k的值 
	int u = root; // 根节点 
	while(tr[u].size >= k) { // 保证当前子树中有解 
		if(tr[tr[u].s[0]].size >= k) u = tr[u].s[0]; // 在左子树中 
		else if(tr[tr[u].s[0]].size + tr[u].cnt >= k) return splay(u, 0), tr[u].v; // 在当前节点 
		else k -= tr[tr[u].s[0]].size + tr[u].cnt, u = tr[u].s[1]; // 在右子树,需要更新k值,减去左子树以及当前节点值的数量 
	}
	return -1;
}

void insert(int v) { // 在二叉树中插入一个值 
	int u = root, p = 0; // p维护为当前节点的父节点 
	while(u && tr[u].v != v) // 没找到则一直向下寻找 
		p = u, u = tr[u].s[v > tr[u].v]; // 更新父节点,更新当前节点 
	if(u) tr[u].cnt ++; // v值的节点已经存在则直接加一即可 
	else { // 不存在则创建节点 
		u = ++ idx; // 分配节点序号 
		if(p) tr[p].s[v > tr[p].v] = u; // 将父节点也就是前驱节点指向当前节点 
		tr[u].init(v, p); // 初始化当前节点的值、父节点信息 
	}
	splay(u, 0); // 将u节点旋转到根节点下
}

void remove(int v) { // 删除一个值为v的节点 
	int prev = get_prev(v), nex = get_next(v); // 获取该节点的前驱以及后继节点。 
	splay(prev, 0), splay(nex, prev); // 将前继节点旋转到根节点,将后继节点旋转到前驱节点下面也就是根节点下面 
	int w = tr[nex].s[0]; // 后继节点的左子树就是v的节点 
	if(tr[w].cnt > 1) tr[w].cnt --, splay(w, 0); // 该节点的v不止存在一个,减一,w节点旋转到根节点 
	else tr[nex].s[0] = 0, splay(nex, 0); // 唯一,那么直接把后继节点的左子树指向空也就是0即可
}

void output(int u) { // 中序遍历输出二叉树 
//	pushdown(u); 
	int l = tr[u].s[0], r = tr[u].s[1]; // 左右儿子 
	if(l) output(l); // 递归左儿子 
	if(tr[u].v >= 1 && tr[u].v <= n) cout <<  tr[u].v << " "; // 输出当前子树的根 
	if(r) output(r); // 递归右儿子 
}

inline void sovle() {

	cin >> n;
	insert(-INF), insert(INF); 
	// 插入两个哨兵,无穷小以及无穷大 使得在查询某数不存在的是时候不会产生越界
	while(n --) {
		int a, b;
		cin >> a >> b;
		if(a == 1) insert(b); // 插入一个值 
		if(a == 2) remove(b); // 插入一个值 
		if(a == 3) cout << get_rank_by_key(b) - 1 << endl; // 真实排名减一 因为前面多了一个哨兵 
		if(a == 4) cout << get_key_by_rank(b + 1) << endl; // 真实排名加一 因为哨兵 
		if(a == 5) cout << tr[get_prev(b)].v << endl; 
		if(a == 6) cout << tr[get_next(b)].v << endl;
	}
}

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

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

相关文章

Another app is currently holding the yum lock; waiting for it to exit...

今天使用yum进行下载的时候报错 解决办法&#xff1a; 执行 rm -f /var/run/yum.pid 然后重新运行yum指令即可&#xff0c;发现已经可以正常下载啦&#xff01;

死磕Nacos系列:Nacos事件发布订阅模型

前言 在Nacos源码中&#xff0c;你是否也经常看到NotifyCenter.publishEvent这样的代码块&#xff1f; 这个事件发布出去后&#xff0c;有哪些类接收到通知并进行了逻辑处理呢&#xff1f; 这里面的实现逻辑是什么呢&#xff1f; 如果你不太清楚&#xff0c;那我们一起来梳理…

企业计算机中了locked勒索病毒怎么解锁,locked勒索病毒解密,数据恢复

科技的进步为企业的生产生活提供了极大便利&#xff0c;但随之而来的网络安全威胁也不断增加&#xff0c;近期云天数据恢复中心陆续接到很多企业的求助&#xff0c;企业的计算机服务器遭到了locked勒索病毒攻击&#xff0c;导致企业的所有业务无法正常开展&#xff0c;所有计算…

Python武器库开发-前端篇之CSS盒模型(三十一)

前端篇之CSS盒模型(三十一) CSS盒模型是指网页中的每个元素可以看做是一个矩形盒子&#xff0c;该盒子有四个主要部分组成&#xff1a;content、padding、border和margin。其中&#xff1a; content&#xff1a;指盒子中的内容区域&#xff0c;可以包含文本、图像、视频、其他…

安装最新版WebStorm来开发JavaScript应用程序

安装最新版WebStorm来开发JavaScript应用程序 Install the Latest Version of JetBrains WebStorm to Develop JavaScript Applications By JacksonML 2023-11-25 1. 系统要求 WebStorm是个跨平台集成开发环境&#xff08;IDE&#xff09;。按照JetBrains官网对WebStorm软件…

Elasticsearch集群部署,配置head监控插件

Elasticsearch是一个开源搜索引擎&#xff0c;基于Lucene搜索库构建&#xff0c;被广泛应用于全文搜索、地理位置搜索、日志处理、商业分析等领域。它采用分布式架构&#xff0c;可以处理大规模数据集和支持高并发访问。Elasticsearch提供了一个简单而强大的API&#xff0c;可以…

【JavaEE初阶】线程安全问题及解决方法

目录 一、多线程带来的风险-线程安全 1、观察线程不安全 2、线程安全的概念 3、线程不安全的原因 4、解决之前的线程不安全问题 5、synchronized 关键字 - 监视器锁 monitor lock 5.1 synchronized 的特性 5.2 synchronized 使用示例 5.3 Java 标准库中的线程安全类…

修改YOLOv5的模型结构第三弹

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 文章目录 任务任务拆解 开始修改C2模块修改yolo.py修改模型配置文件 模型训练 上次已…

rfc4301- IP 安全架构

1. 引言 1.1. 文档内容摘要 本文档规定了符合IPsec标准的系统的基本架构。它描述了如何为IP层的流量提供一组安全服务&#xff0c;同时适用于IPv4 [Pos81a] 和 IPv6 [DH98] 环境。本文档描述了实现IPsec的系统的要求&#xff0c;这些系统的基本元素以及如何将这些元素结合起来…

第十三章 深度解读预训练与微调迁移,模型冻结与解冻(工具)

一个完整的代码 pythonCopy codeimport torch import torchvision import torchvision.transforms as transforms import torch.nn as nn import torch.optim as optim # 设置设备&#xff08;CPU或GPU&#xff09; device torch.device("cuda" if torch.cuda.is_a…

canvas扩展001:利用fabric绘制图形,可以平移,旋转,放缩

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 示例…

stm32实现0.96oled图片显示,菜单功能

stm32实现0.96oled图片显示&#xff0c;菜单功能 功能展示简介代码介绍oled.coled.holedfont.h&#xff08;字库文件&#xff09;main函数 代码思路讲解 本期内容&#xff0c;我们将学习0.96寸oled的进阶使用&#xff0c;展示图片&#xff0c;实现菜单切换等功能&#xff0c;关…

MySQL日期函数sysdate()与now()的区别,获取当前时间,日期相关函数

select sleep(2) as datetime union all select sysdate() -- sysdate() 返回的时间是当前的系统时间&#xff0c;而 now() 返回的是当前的会话时间。 union all select now() -- 等价于 localtime,localtime(),localtimestamp,localtimestamp(),current_timestamp,curre…

基于鱼鹰算法优化概率神经网络PNN的分类预测 - 附代码

基于鱼鹰算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于鱼鹰算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于鱼鹰优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

程序的编译与链接(详解)

程序的编译与链接 本章内容如下&#xff1a; 1:程序的翻译环境与执行环境的介绍 2:详解程序的翻译环境(编译链接) 2.1预处理阶段干了啥2.2编译阶段干了啥2.3汇编阶段干了啥2.4链接阶段干了啥 3:预处理详解 预定义符号的介绍#define 的介绍(宏与标识符号)#与##的介绍宏与函数…

对象的内部结构

在HotSpot 虚拟机里&#xff0c;对象在堆内存中的存储布局可以划分为三个部分&#xff1a;对象头&#xff08; Header &#xff09;、实例数据&#xff08;Instance Data &#xff09;和对齐填充&#xff08; Padding &#xff09;。 对象头 Mark Word&#xff08;标记字段&a…

RK3568驱动指南|第八篇 设备树插件-第72章 设备树插件语法和编译实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

[element-ui] el-dialog 中的内容没有预先加载,因此无法获得内部元素的ref 的解决方案

问题描述 在没有进行任何操作的时候&#xff0c;使用 this.$refs.xxxx 无法获取el-dialog中的内部元素&#xff0c;这个问题会导致很多bug. 官方解释&#xff0c;在open事件回调中进行&#xff0c;但是open()是弹窗打开时候的会调&#xff0c;有可能在此处获取的时候&#xff…

教师授课技巧

一名教师&#xff0c;授课技巧是提高教学效率和质量的关键。以下是几个实用的授课技巧&#xff0c;可以帮你更好的传授知识&#xff0c;激发学习兴趣。 一、做好课前准备 课前准备是授课技巧的重要环节。认真备课&#xff0c;熟悉教材内容&#xff0c;制定教学计划&#xff0c…

redis运维(二十一)redis 的扩展应用 lua(三)

一 redis 的扩展应用 lua redis加载lua脚本文件 ① 调试lua脚本 redis-cli 通过管道 --pipe 快速导入数据到redis中 ② 预加载方式 1、错误方式 2、正确方式 "案例讲解" ③ 一次性加载 执行命令&#xff1a; redis-cli -a 密码 --eval Lua脚本路径 key …