P3369 【模板】普通平衡树(splay 算法)

news2024/10/9 18:18:05

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入一个数 x。
  2. 删除一个数 x(若有多个相同的数,应只删除一个)。
  3. 定义排名为比当前数小的数的个数 +1。查询 x 的排名。
  4. 查询数据结构中排名为 x 的数。
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)。
  6. 求 x 的后继(后继定义为大于 x,且最小的数)。

对于操作 3,5,6,不保证当前数据结构中存在数 x。

输入格式

第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)

输出格式

对于操作 3,4,5,6 每行输出一个数,表示对应答案。

输入输出样例

输入 #1复制

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出 #1复制

106465
84185
492737

说明/提示

【数据范围】
对于 100% 的数据,1≤n≤105,∣x∣≤107

解析:

这时一个思维的跨度:

我们自己构造平衡二叉树,左儿子节点小于父节点,右儿子的全部节点大于父节点。

我们需要对二叉树进行左转和右转。

每次进行转动时替代其相对的儿子。

比如:

右旋时:x的右儿子当作y的左儿子。

这里你们可能会问为什么当y的左儿子呢?

答:因为这是一颗平衡二叉树,左儿子一定小于父节点。

而且x为y的左儿子,x的任意一个儿子一定小于其x的父节点。
左旋同理。

实现代码是用异或操作,刚好就是其儿子转动的对立面。比如:x右旋,x的右儿子为y,这时原本的x的右儿子就要当y的左儿子。这样子x的右儿子不会丢失。又满足平衡二叉树的性质。

实现左旋右旋代码如下:

void rotate(int x) // 可以进行左旋 和右旋 
{
	//先修 z ;在修 y ;在修 x的儿子节点;在修 x本身 
	int y = tr[x].fa,z = tr[y].fa, k = tr[y].ch[1] == x; // z -> y -> x  我们需要 将 它转为 z->x->y; 
	tr[z].ch[tr[z].ch[1] ==y]  = x, tr[x].fa = z; //  z的 儿子 为 y 替换为 x , 在将 x 的父节点 为 x
	tr[y].ch[k] = tr[x].ch[k^1];//比如如果 初始 y的左儿子为 x 当替换完。 y的左儿子空了 ,将x的右儿子给y的做儿子 
	tr[tr[x].ch[k^1]].fa = y; // 从x下的儿子 替换到 y 下时  ,儿子的父节点要变 
	tr[x].ch[k^1] = y;//这时x那个替换到 y的儿子空位了。
	tr[y].fa = x;
	pushup(y);//先push儿子在父节点 
	pushup(x); 
}

下面就是我们如何维护这个平衡二叉树的树高,尽可能使它的高度最小。

我学到的方法是, 当一棵树他是以直线型,折线型时,如下图所示:

当直线型时,先旋转它的父节点,在旋转它自己本身。

当折线型时,先旋转它自己,在旋它自己。

代码如下:

void splay(int x,int k) //k为父节点 
{
	while(tr[x].fa != k)
	{
		int y = tr[x].fa,z = tr[y].fa;//从 x起 2个父节点  
		if(z != k){ // 如果 是 一条    有折线时 
		//如果 时一条 直线 时 1^1 为 false ,先选 y 在旋 x  
			(ls(y) == x)^(ls(z)==y) ? rotate(x):rotate(y);
		}
		rotate(x);
	}
	if(!k){//等于 0 的时候 才为 根节点 
		root = x;
	}
}

还有一个前驱操作:

先用find函数这个节点在那里。

在遍历它的左儿子的右儿子。到达最右的那个儿子。

后继也是一样。

void find(int v)
{
	int x = root;
	while(tr[x].ch[v > tr[x].v] && v!=tr[x].v)
	{
		x = tr[x].ch[v > tr[x].v];
	}
	splay(x,0);
}

int getpre(int v)
{
	find(v);
	int x = root;
	if(tr[x].v < v){
		return x;
	}
	x = ls(x);
	while(rs(x)){
		x = rs(x);
	}
	splay(x,0);
	return x;
}

int getsuc(int v)
{
	find(v);
	int x = root;
	if(tr[x].v > v) return x;
	x = rs(x);
	while(ls(x)) x = ls(x);
	splay(x,0);
	return x; 
}

删除操作:

我们找到它的前驱和后继,再进行把删除的点移到其后继的左儿子上,再进行删除操作。

这个需要画图理解一下。

void del(int v)//删除 
{
	int pre = getpre(v);
	int suc = getsuc(v);
	splay(pre,0);
	splay(suc,pre);//将它转到左节点方便删除
	int del = tr[suc].ch[0];
	if(tr[del].cnt > 1)
	{
		tr[del].cnt--;
		splay(del,0);	
	} 
	else{
		tr[suc].ch[0] = 0;
		splay(suc,0);
	}
}

刚开始插入操作时,我们要进行哨兵的插入。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;

#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
const int N = 1100010,INF = (1<<30)-1;
struct node{
	int ch[2];
	int fa;
	int v;
	int cnt;
	int siz;
	void init(int p,int v1){
		fa = p;
		v = v1;
		cnt = siz  = 1;
	}
}tr[N];
int root = 0,tot = 0;
void pushup(int x)
{
	tr[x].siz = tr[ls(x)].siz + tr[rs(x)].siz + tr[x].cnt;
}

void rotate(int x) // 可以进行左旋 和右旋 
{
	//先修 z ;在修 y ;在修 x的儿子节点;在修 x本身 
	int y = tr[x].fa,z = tr[y].fa, k = tr[y].ch[1] == x; // z -> y -> x  我们需要 将 它转为 z->x->y; 
	tr[z].ch[tr[z].ch[1] ==y]  = x, tr[x].fa = z; //  z的 儿子 为 y 替换为 x , 在将 x 的父节点 为 x
	tr[y].ch[k] = tr[x].ch[k^1];//比如如果 初始 y的左儿子为 x 当替换完。 y的左儿子空了 ,将x的右儿子给y的做儿子 
	tr[tr[x].ch[k^1]].fa = y; // 从x下的儿子 替换到 y 下时  ,儿子的父节点要变 
	tr[x].ch[k^1] = y;//这时x那个替换到 y的儿子空位了。
	tr[y].fa = x;
	pushup(y);//先push儿子在父节点 
	pushup(x); 
}

void splay(int x,int k) //k为父节点 
{
	while(tr[x].fa != k)
	{
		int y = tr[x].fa,z = tr[y].fa;//从 x起 2个父节点  
		if(z != k){ // 如果 是 一条    有折线时 
		//如果 时一条 直线 时 1^1 为 false ,先选 y 在旋 x  
			(ls(y) == x)^(ls(z)==y) ? rotate(x):rotate(y);
		}
		rotate(x);
	}
	if(!k){//等于 0 的时候 才为 根节点 
		root = x;
	}
}

void insert(int v) //插入 节点  
{
	int x = root,p = 0;
	while(x&& tr[x].v != v){ // 弹出 条件 x 为 0节点 找不到  或者 找到当前节点  
		p = x, x = tr[x].ch[v > tr[x].v];
	}
	
	if(x) {
		tr[x].cnt++;
	}
	else{ // 新创建 一个节点 
		x = ++tot;
		tr[p].ch[v>tr[p].v] = x;
		tr[x].init(p,v); 
	}
	splay(x,0);
}


void find(int v)
{
	int x = root;
	while(tr[x].ch[v > tr[x].v] && v!=tr[x].v)
	{
		x = tr[x].ch[v > tr[x].v];
	}
	splay(x,0);
}

int getpre(int v)
{
	find(v);
	int x = root;
	if(tr[x].v < v){
		return x;
	}
	x = ls(x);
	while(rs(x)){
		x = rs(x);
	}
	splay(x,0);
	return x;
}

int getsuc(int v)
{
	find(v);
	int x = root;
	if(tr[x].v > v) return x;
	x = rs(x);
	while(ls(x)) x = ls(x);
	splay(x,0);
	return x; 
}

void del(int v)//删除 
{
	int pre = getpre(v);
	int suc = getsuc(v);
	splay(pre,0);
	splay(suc,pre);//将它转到左节点方便删除
	int del = tr[suc].ch[0];
	if(tr[del].cnt > 1)
	{
		tr[del].cnt--;
		splay(del,0);	
	} 
	else{
		tr[suc].ch[0] = 0;
		splay(suc,0);
	}
}

int getrank(int v)
{
	insert(v);
	int res = tr[tr[root].ch[0]].siz;
	del(v);
	return res;
}

int getval(int k) //第k个值 
{
	int x = root;
	while(true)
	{
		if(k <= tr[ls(x)].siz) x = ls(x);
		else if(k <= tr[ls(x)].siz + tr[x].cnt) break;
		else k -= tr[ls(x)].siz + tr[x].cnt,x = rs(x);
	}
	splay(x,0);
	return tr[x].v;
}


int main()
{
	insert(-INF);//加入哨兵 
	insert(INF);
	int n,op,x;
	scanf("%d",&n);
	while(n--)
	{
		scanf("%d%d",&op,&x);
		if(op == 1){
			insert(x); 
		}
		else if(op == 2){
			del(x);
		}
		else if(op == 3)
		{
			printf("%d\n",getrank(x));
		}else if(op == 4){
			printf("%d\n",getval(x+1)); //因为有哨兵 
		}else if(op == 5)
		{
			printf("%d\n",tr[getpre(x)].v);
		}else{
			printf("%d\n",tr[getsuc(x)].v);
		}
	}
	return 0;	
}  

时间复杂度为:O(n*logn)

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

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

相关文章

6、父子组件传参、路由的嵌套、命名视图、路由跳转传参

一、父子组件传参 1、父传子 在父组件的子组件中自定义一个属性在子组件中有一个props属性&#xff0c;用来接收父组件传递的数据,传递的数据不能修改,还可以设置默认值 <!-- 父组件 -->data() {return {flag: false,num:10, //传的参数free:}} <!-- :type1"…

【JavaScript算法】DOM树层级显示

题目描述&#xff1a; 上述表达式的输出结果为 [DIV] [P, SPAN, P, SPAN] [SPAN, SPAN]直接上代码 let tree document.querySelector(".a"); function traverseElRoot(elRoot) {const result [];function traverse(element, level) {if (!result[level]) {resul…

【系统架构师】-第15章-面向服务架构设计

面向服务的体系结构 (Service-Oriented Architecture,SOA) 1、应用角度&#xff1a;它着眼于日常的业务应用&#xff0c;并将它们划分为单独的业务功能和流程&#xff0c;即所谓的服务 2、软件基本原理&#xff1a;一个组件模型&#xff0c;它将应用程序的不同功能单元(称为服…

Python数据分析必备工具——Pandas模块及其应用

Python数据分析必备工具——Pandas模块及其应用 外部数据的读取文本文件的读取语法示例 电子表格的读取语法示例 数据库数据的读取与操作语法 数据操作数据概述语法 数据筛选语法 数据清洗数据类型语法示例 沉余数据语法示例 异常值的识别与处理缺失值的识别与处理语法示例 数据…

PHP图床程序优化版:图片外链服务、图床API服务、图片CDN加速与破解防盗链

图片免费上传 支持本地储存、FTP储存、第三方云储存&#xff08;阿里云 OSS、腾讯云 COS、七牛云等&#xff09;。 图片外链加速 一键转换第三方网站的图片外链地址为图床可分享的图片地址&#xff08;支持CDN&#xff09;。 图片解析服务 直接将第三方外链图片地址显示为…

BSV区块链的应用开发前景——通过标准化来促进创新

​​发表时间&#xff1a;2024年3月5日 近年来区块链领域的发展日新月异&#xff0c;各种全新的技术和方法论正在迅猛涌现。在这个瞬息万变的环境之中&#xff0c;标准化不仅仅会为开发者们带来便利&#xff0c;同时也促进了应用之间的互操作性&#xff0c;并且推动着生态系统的…

【机器学习300问】56、什么是自编码器?

一、什么是自编码器&#xff1f; 自编码器&#xff08;Autoencoder&#xff0c;AE&#xff09;本质是一种特殊的神经网络架构。主要用于无监督学习和特征学习任务。它的目标是通过编码然后解码的过程&#xff0c;学会重构其输入数据&#xff0c;试图还原其原始输入的。 当时我学…

【探索Linux】—— 强大的命令行工具 P.31(守护进程)

阅读导航 引言一、守护进程简介1. 概念2. 特点 二、用C创建守护进程⭕代码✅主要步骤 温馨提示 引言 当谈到计算机系统中运行的特殊进程时&#xff0c;守护进程&#xff08;daemon&#xff09;无疑是一个备受关注的话题。作为在后台默默运行并提供各种服务的进程&#xff0c;守…

FreeRTOS从代码层面进行原理分析(4 移植)

FreeRTOS从代码层面进行原理分析(4 移植) 从前 3 篇博客中我们已经搞清楚了最开始对 FreeRTOS 有疑问的前 2 个问题。 1. FreeRTOS 是如何建立任务的呢&#xff1f; 2. FreeRTOS 是调度和切换任务的呢&#xff1f; 3. FreeRTOS 是如何保证实时性呢&#xff1f; 以下就是前三…

LeetCode:300最长递增子序列 C语言

300. 最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子…

大话设计模式之迪米特法则

迪米特法则&#xff0c;也称为最少知识原则&#xff08;Law of Demeter&#xff09;&#xff0c;是面向对象设计中的一个重要原则&#xff0c;其核心思想是降低耦合度、减少对象之间的依赖关系&#xff0c;从而使系统更加灵活、易于维护和扩展。 根据迪米特法则&#xff0c;一…

Multisim14.0破解安装教程

Multisim14.0中文破解版是一款相当优秀的专业化SPICE仿真标准环境&#xff0c;Multisim14.0中文版功能强悍&#xff0c;为用户提供了所见即所得的设计环境、互动式的仿真界面、动态显示元件、具有3D效果的仿真电路、虚拟仪表、分析功能与图形显示窗口等等。Multisim破解版操作简…

Linux-1.常见指令以及权限理解

目录 本节目标 使用 XShell 远程登录 Linux 关于 Linux 桌面 下载安装 XShell 查看 Linux 主机 ip 使用 XShell 登陆主机 XShell 下的复制粘贴 Linux下基本指令 登录Linux服务器 新建多用户 全屏 1.快速认识5~6个命令 2.详细谈论课件的所有指令 01. ls 指令 02…

Linux 环境安装Nginx—源码和Dokcer两种安装方式

一、源代码编译安装Nginx 1.下载最新nginx源码 以nginx-1.25.3.tar.gz为例&#xff1a; 可以使用命令(联网)&#xff1a;curl -O http://nginx.org/download/nginx-1.25.3.tar.gz或在官网下载.tar.gz 2.解压缩 tar -zxvf nginx-1.25.3.tar.gz cd nginx-1.25.3/ 3.安装依赖…

动态菜单设计

查询当前用户下的菜单权限 思路&#xff1a;根据用户id 左关联表 查询出对应的菜单选项 查询SQL select distinct-- 菜单表 去除重复记录sys_menu.id,sys_menu.parentId, sys_menu.name from -- 权限表sys_menu-- 角色与权限表 菜单表id 角色菜单表的菜单id left j…

数据分析之Power BI

POWER QUERY 获取清洗 POWER PIVOT建模分析 如何加载power pivot 文件-选项-加载项-com加载项-转到 POWER VIEW 可视呈现 如何加载power view 文件-选项-自定义功能区-不在功能区中的命令-新建组-power view-添加-确定 POWER MAP可视地图

Redis 6.0.8版本下载

简介&#xff1a;Java领域优质创作者楠木 分享Java项目、简历模板、学习资料、面试题库 想必大家在下载redis之前已经逛了很多教程了&#xff0c;可能不尽如意&#xff0c;找不到自己的想要的版本。既然刷到了我的这条博客&#xff0c;说明你是幸运的&#xff01; Redis6.0.8的…

k8s1.28.8版本配置prometheus监控告警

文章目录 官方架构图组件的具体介绍kube-prometheus包含的组件简介&#xff1a;文件存储路径&#xff1a; 结构分析官网自带的一些规则自己总结流程 1-创建规则磁盘使用率报警规则 详解上面rule流程Alertmanagerg查看 2-报警接收器2.1-邮件报警修改Alertmanager配置查看现有的s…

【深耕 Python】Data Science with Python 数据科学(2)jupyter-lab和numpy数组

关于数据科学环境的建立&#xff0c;可以参考我的博客&#xff1a;【深耕 Python】Data Science with Python 数据科学&#xff08;1&#xff09;环境搭建 Jupyter代码片段1&#xff1a;简单数组的定义和排序 import numpy as np np.array([1, 2, 3]) a np.array([9, 6, 2, …

flume配置文件后不能跟注释!!

先总结&#xff1a;Flume配置文件后面&#xff0c;不能跟注释&#xff0c;可以单起一行写注释 报错代码&#xff1a; [ERROR - org.apache.flume.SinkRunner$PollingRunner.run(SinkRunner.java:158)] Unable to deliver event. Exception follows. org.apache.flume.EventDel…