平衡树 - splay

相比于之前的普通平衡树进行左旋右旋来比,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;
	}
}

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

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

相关文章

leetcode面试经典150题——32 串联所有单词的子串(中等+困难)

题目&#xff1a; 串联所有单词的子串(1中等) 描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&…

人力资源管理后台 === 员工新增修改

目录 1.员工管理-导出excel 2.员工管理-excel组件封装 3.员工管理-下载导入模板 4.员工管理-员工导入-上传excel 5.员工管理-删除员工 6.员工详情和路由 7.员工详情-表单数据校验 8.员工详情-封装部门级联组件 9.员工详情-级联组件-双向绑定 10.员工详情-新增员工 11…

async函数和await关键字

async写在一个函数a前面&#xff0c;该函数变为异步函数&#xff0c;可在里面使用await关键字&#xff0c;await后面一般跟一个promise对象&#xff08;axios函数返回一个promise对象&#xff0c;里面有异步任务&#xff09;&#xff0c;await会原地等待该异步任务结果&#xf…

Horizon地平线财富一直坚持“创新、开放、协作、共享”的运营理念

在“寒风凛冽”的熊市&#xff0c;投资人需要一颗不断探索、勇于尝试的心。 勇气意味着即使你知道这条路很难&#xff0c;你仍然选择坚持。而信念则是相信&#xff0c;即使现在很多人不理解、甚至嘲笑&#xff0c;未来总会有一天他们会明白。 Horizon一直坚持着“创新、开放、…

[计算机网络]应用层概述

0.写在前面: 该层为教学模型的最后一层,某种意义上来说是最接近各位开发者的一层,正因如此,这层中的很多定义和概念大家都有属于自己的理解, 完全按照书本反而才是异类,因此在这里我会去结合我做前端开发的一些经验,来处理和讲解一些概念,另外本层中的部分协议也不会过多阐述了…

【Amazon】通过代理连接的方式导入 AWS EKS集群至KubeSphere主容器平台

文章目录 一、设置主集群方式一&#xff1a;使用 Web 控制台方式二&#xff1a;使用 Kubectl命令 二、在主集群中设置代理服务地址方式一&#xff1a;使用 Web 控制台方式二&#xff1a;使用 Kubectl命令 三、登录控制台验证四、准备成员集群方式一&#xff1a;使用 Web 控制台…

RC-MVSNet:无监督的多视角立体视觉与神经渲染--论文笔记(2022年)

RC-MVSNet&#xff1a;无监督的多视角立体视觉与神经渲染--论文笔记&#xff08;2022年&#xff09; 摘要1 引言2 相关工作2.1 基于监督的MVS2.2 无监督和自监督MVS2.3 多视图神经渲染 3 实现方法3.1 无监督的MVS网络 Chang, D. et al. (2022). RC-MVSNet: Unsupervised Multi-…

Oracle(2-6) Backup and Recovery Overview

文章目录 一、基础知识1、Categories of Failures 故障类别2、Causes of Statement Failures 语句失败的原因故障情况Resolutions 决议 3、User Process Failures 用户进程失败故障情况Resolutions 决议 4、Possible User Errors 用户错误类型故障情况Resolutions 决议 5、Inst…

SpringCloud原理-OpenFeign篇(四、请求原理)

文章目录 前言正文一、书接上回&#xff0c;从代理对象入手二、ReflectiveFeign.FeignInvocationHandler#invoke()三、SynchronousMethodHandler#invoke(...) 的实现原理3.1 invoke(...)源码3.2 executeAndDecode(...) 执行请求并解码 四、如何更换client 的实现 附录附1&#…

【Linux】记录错误信息日志的实现

文章目录 前言一、 目录实现&#xff08;log.hpp&#xff09;二、目录的具体使用1.comm.hpp&#xff08;管道初始化&#xff09;2.sever.cpp&#xff08;为读端且令其创建命名管道&#xff09;3.client.cpp(为写端) 前言 我们这个设计的日志可以自定以输出的方向&#xff0c;可…

领域驱动设计总结——如何构造领域模型

领域驱动设计总结——如何构造领域模型 本文为领域驱动设计系列总结的第三篇&#xff0c;主要对领域驱动设计概念做个介绍&#xff0c;本系列领域驱动设计总结主要是在Eric Evans 所编写的《领域驱动设计》 一书的基础上进行归纳和总结。本文主要介绍在领域驱动设计中如何构造…

OpenWrt Lan口上网设置

LAN口上网设置 连接上openwrt&#xff0c;我用的 倍控N5105&#xff0c;eth0&#xff0c;看到Openwrt的IP是10.0.0.1 在 网络 -> 网口配置 -> 设置好 WAN 口和 LAN 口 初次使用经常重置 openwrt 所以我设置的是 静态IP模式 - 网络 -> 防火墙 -> 常规设置 ->…

【深度学习实验】图像处理(二):PIL 和 PyTorch(transforms)中的图像处理与随机图片增强

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 导入需要的工具包1. PIL图像处理a. 生成绿色和蓝色图像b. 缩放和合成图像c 在合成图像上添加文字d. 展示并保存图像 2. PIL随机图像增强a. 定义随机图像增强函数b. 实验结果展示 3. PyTorch&…

FloodFill

"绝境之中才窥见&#xff0c;Winner&#xff0c;Winner" FloodFill算法简介: floodfill又翻译成漫水填充。我们可以将下面的矩阵理解为一片具有一定高度的坡地&#xff0c;此时突发洪水&#xff0c;洪水会将高度<0的地方填满。 话句话来说&#xff0c;Fl…

uniapp+vue基于Android的校园二手跳蚤市场的设计与实现 微信小程序

实现功能&#xff1a; 用户管理&#xff1a;登陆、注册、注销、修改密码、上传头像、修改资料 发布与检索&#xff1a;发布商品、模糊搜索、人气排序、价格排序、时间排序、推送商品&#xff08;协同过滤算法实现个性化推荐&#xff09;&#xff0c;最新发布、分类检索 核心交易…

解密Kafka主题的分区策略:提升实时数据处理的关键

目录 一、Kafka主题的分区策略概述1.1 什么是Kafka主题的分区策略&#xff1f;1.2 为什么分区策略重要&#xff1f; 二、Kafka默认分区策略2.1 Round-Robin分区策略 三、自定义分区策略3.1 编写自定义分区器3.2 最佳实践&#xff1a;如何选择分区策略 四、分区策略的性能考量4.…

【数据中台】开源项目(2)-Dbus数据总线

1 背景 企业中大量业务数据保存在各个业务系统数据库中&#xff0c;过去通常的同步数据的方法有很多种&#xff0c;比如&#xff1a; 各个数据使用方在业务低峰期各种抽取所需数据&#xff08;缺点是存在重复抽取而且数据不一致&#xff09; 由统一的数仓平台通过sqoop到各个…

error LNK2038: 检测到“RuntimeLibrary”的不匹配项 解决方法

问题&#xff1a; 我们在使用Visual Studio编程的时候偶尔会遇到以下三种报错&#xff1a; error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MD_DynamicRelease”不匹配值“MDd_DynamicDebug” &#xff08;引用的是release模式&#xff0c;但设置成debug模式了…

【研究中2】sql server权限用户设置

--更新时间2023.11.26 21&#xff1a;30 负责人&#xff1a;jerrysuse DBAliCMSIF EXISTS (select * from sysobjects where namehkcms_admin)--判断是否存在此表DROP TABLE hkcms_adminCREATE TABLE hkcms_admin (id int identity(1, 1),--id int primary key identity…

本地运行“李开复”的零一万物 34B 大模型

这篇文章&#xff0c;我们来聊聊如何本地运行最近争议颇多的&#xff0c;李开复带队的国产大模型&#xff1a;零一万物 34B。 写在前面 零一万物的模型争议有很多&#xff0c;不论是在海外的社交媒体平台&#xff0c;还是在国内的知乎和一种科技媒体上&#xff0c;不论是针对…