平衡树专题Splay

写在前面: 部分来自孙宝(@Steven24)的博客,表示感谢。

认识

什么是Splay

就是BST的一种,整体效率是很高的,均摊的次数是O(logn)级别的。

基本操作就是把节点旋转到BST的root,从而改善BST的平衡性,但是很多人会在旋转中转晕

建议找个动图看看,或是上B站找个几分钟的视频看看就理解了。

烧烤

如何可以把一个节点旋转到BST的root的地方,这是Splay的核心,为了完成这个操作,我们主要是需要分成两步:

1.每旋转一次,就使得我们的目标节点x的层次上升一层,最后到达root层

2.在旋转完后,BST的平衡性被破坏了,这是毋庸置疑的,所以我们需要进行一系列的调整,使这棵BST重新回到平衡状态

显然,如果只考虑1,那么使用Treap树的旋转法即可,每次x与x的父亲交换位置(x上升一层)。可Treap树的这种“单旋”并不能减少BST的层数。

所以我们就升级一下,再拉来一个更能打的,祖父节点,让这三代人一起转。

Splay旋转

#define left 0,right 1

这个就是LL,RR,LR,RL四个Splay的基本模型

网上有很多

自己看吧

我推荐这个,因为我就是看着他弄懂的基础知识

Splay四种模型

Splay操作

由于支持单点旋转改变平衡因子的特性Splay常用于处理区间分裂和合并问题。

例如:一个常见的区间操作,修改或查询区间[L,R],用Splay树就很容易实现:先把L-1旋转到根,然后把节点R+1旋转到L-1的右子树上,此时,L+1的左子树就是区间[L,R]。

1.旋转:

rotate(x)对x点进行一次旋转,若x是一个右儿子,左旋,反之亦反之。

Splay Rotate


void rotate(int x)//单旋一次
{
	int f=t[x].fa;//f:父亲
	int g=t[f].fa;//g:祖父
	int son=get(x);
	if(son==1)//x是左儿子,右旋
	{
		t[f].rs=t[x].ls;
		if(t[f].rs)
		{
			t[t[f].rs].fa=f;
		}
	}
	else//x是右儿子,左旋
	{
		t[f].ls=t[x].rs;
		if(t[f].ls)
		{
			t[t[f].ls].fa=f;
		}
	}
	t[f].fa=x;//x旋为f的父节点
	if(son==1)//左旋,f变为x的左儿子
	{
		t[x].ls=f;
	}
	else//右旋,f变为x的右儿子
	{
		t[x].rs=f;
	}
	t[x].fa=g;//x现在是祖父的儿子
	if(g)//更新祖父的儿子
	{
		if(t[g].rs==f)
		{
			t[g].rs=x;
		}
		else
		{
			t[g].ls=x;
		}
	}
	Update(f);
	Update(x);
}

Splay(int x,int goal),把节点x旋转到goal位置。goal=0表示把x旋转到根,x是新的根。goal≠0表示把x旋转为goal的儿子。

Splay Rotate Destiny

 void Splay(int x,int goal)
{
	if(goal==0)
	{
		root=x;
	}
	while(1)
	{
		int f=t[x].fa;//一次处理x,f,g三个节点
		int g=t[f].fa;
		if(f==goal){
			break;
		}
		if(g!=goal)//有祖父,分为一字旋和之字旋两种情况
		{
			if(get(x)==get(f))//一字旋,先旋转f,g
			{
				rotate(f);
			}
			else//之字旋,直接旋转x
			{
				rotate(x);
			}
		}
		rotate(x);
	}
	Update(x);
} 

2.分裂和合并:

Insert()、Del()函数中包含了分裂与合并,详情见代码注释。利用Splay函数实现分裂与合并,编码很简单。

Splay Insert and Delete

 void Insert(int L,int len)//插入一段区间
{
	int x=kth(root,L);//x为第L个数的位置,y为第L+1个数的位置
	int y=kth(root,L+1);
	Splay(x,0);//分裂
	Splay(y,x);
    //先把x旋转到根,然后把y旋转到x的儿子,且y的儿子为空
	t[y].ls=build(1,len,y);//合并:建一棵树,挂到y的左儿子上
	Update(y);
	Update(x);
}
void Del(int L,int R)//删除区间[L+1,R]
{
	int x=kth(root,L);
	int y=kth(root,R+1);
	Splay(x,0);//y是x的右儿子,y的左儿子是待删除的区间
	Splay(y,x);
	t[y].ls=0;//剪短左子树,等于直接删除,这里为了简单,没有释放空间
	Update(y);
	Update(x);
}

3.查询:

Splay Find

 int kth(int x) { //查询排名为x的数 
	int now = root;
	while (1) {
		if (siz[son[now][0]] >= x) now = son[now][0]; //查过头了
		else if (siz[son[now][0]] + cnt[now] >= x) return val[now]; //正好查到
		else {
			x -= (siz[son[now][0]] + cnt[now]);
			now = son[now][1]; //没查够 
		} 
	}
}

Splay Rank

 int rank(int x) { //查询x的排名 
	int now = root, ans = 0;
	while (1) {
		if (!now) return ans + 1; //树中不存在这个数 那就是目前查到比它小的数的数目+1
		if (val[now] > x) now = son[now][0]; //要查的数比now节点的数小 说明在左子树
		else if (val[now] == x) {
			ans += siz[son[now][0]]; //比它小的数的数目
			splay(now);
			return ans + 1; 
		} else { //要查的数在左子树 
			ans += siz[son[now][0]] + cnt[now]; //此时当前节点和左子树所有数都比它小 
			now = son[now][1];
		}
	} 
}

Splay Find pre/nxt

 int findpre() { //查询根节点的前驱对应的结点编号 
	int now = son[root][0]; //首先进入左子树 因为左子树的所有数都比它小 
	while (son[now][1]) now = son[now][1]; //然后一直往大的走 找最大的
	return now; 
} 

int findnxt() { //跟上面同理 
	int now = son[root][1];
	while (son[now][0]) now = son[now][0];
	return now;
}

···

else if (opt == 5) {
	splay.insert(x); //把x先旋到根节点 而且一定要插入而不是直接旋 防止树上本来就没有x
	printf("%d\n", splay.val[splay.findpre()]);
	splay.del(x); //用完删掉 
} else {
	splay.insert(x); //同上 
	printf("%d\n", splay.val[splay.findnxt()]);
	splay.del(x);
}

注意

(感谢孙宝)

所以 splay 容易写挂的原因找到了 要是哪个 splay 和 maintain 忘写了就寄了
那么怎么记住到底哪要写哪不用写呢

对于 maintain 很简单 改了啥就把它自己 maintain 一下 再把父亲(如果有)也 maintain 一下
对于 splay 实际上我们使用这个函数的同时如果要实现提根的功能 那就写
比如 insert 我们查询前驱后继需要使用它并且把插入的数旋到根 所以要写
比如 rank 我们就是要用它把权值为 x 的结点旋到根节点 所以要写
再比如 del 呃这个你肯定不能忘

如果还记不住怎么办呢

首先要明确 splay 是复杂度的保证 写少了复杂度就会假
但是写几个肯定是没事的
maintain 也是 如果该更新的没更新肯定就寄了
但是把没必要更新的也更新了问题就不大
所以实在不行这俩就是能写就写

当然这么干常数就又会变大 所以最好还是记一下上面那个

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

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

相关文章

线性代数大题细节。

4.4 方程组解的结构(二)_哔哩哔哩_bilibili

无序中的秩序:为何看似混乱的工作方式可能更高效;刚刚!研究表明:混乱可能更有利于创造力;注意!你的过度整理可能正在浪费时间

当面对循规蹈矩,还是自由独立的选择题时,你应当选择自由独立。因为这样,你不但更省力,更省心,而且效率更高,生活更好。 在日常生活和工作中,经常会遇到两种截然不同的人: • 一种是事无巨细,将一切都安排得…

全面了解机器学习

目录 一、基本认识 1. 介绍 2. 机器学习位置 二、机器学习的类型 1. 监督学习 2. 无监督学习 3. 强化学习 三、机器学习术语 1. 训练样本 2. 训练 3. 特征 4. 目标 5. 损失函数 四、机器学习流程 五、机器学习算法 1. 分类算法 2. 聚类算法 3. 关联分析 4. …

红队工具Finger 安装具体以步骤-示例centos

1.git clone https://github.com/EASY233/Finger.git 如果没有 yum install git 2.pip3 install -r requirements.txt 找到finger所在的文件夹 可以用find -name "Finger"进入文件中配置命令 前提要安装python yum install python-pip33.python3 Finger.py -h

Databend 开源周报第 151 期

Databend 是一款现代云数仓。专为弹性和高效设计,为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务:https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展,遇到更贴近你心意的 Databend。 支持递归调用 UD…

浅谈k8s中cni0和docker0的关系和区别

最近在复习k8s网络方面的知识,查看之前学习时整理的笔记和文档还有过往自己总结的博客之后发现一个问题,就是在有关flannel和calico这两个k8s网络插件的文章和博客中,会涉及到cni0和docker0这两个网桥设备,但是都没有明确说明他们…

新华三通用大模型算力底座方案:为AI时代注入强大动力

在人工智能技术日新月异的今天,大模型作为推动AI进步的重要驱动力,是百行百业不断追逐的热点。大模型以其强大的泛化能力、卓越的模型效果和广泛的应用场景,正改变着人工智能的未来。作为国内领先的ICT解决方案提供商,新华三集团凭…

【刷题汇总--牛牛的快递、最小花费爬楼梯、数组中两个字符串的最小距离】

C日常刷题积累 今日刷题汇总 - day0021、牛牛的快递1.1、题目1.2、思路1.3、程序实现1.4、程序实现(扩展) 2、最小花费爬楼梯2.1、题目2.2、思路2.3、程序实现 3、数组中两个字符串的最小距离3.1、题目3.2、思路3.3、程序实现3.4、补充0x3f3f3f3f 4、题目链接 今日刷题汇总 - d…

解码未来城市:探秘数字孪生的奥秘

在科技日新月异的今天,"数字孪生"(Digital Twin)这一概念如同一颗璀璨的新星,照亮了智慧城市、智能制造等多个领域的前行之路。本文将深入浅出地解析数字孪生的定义、技术原理、应用场景及未来发展,带您一窥…

亚马逊TM商标跟卖,同行截流采集,人工手动跟卖选品更方便!

区分TM标,软件自动查询,人工手动查询方便。 大家好,跟大家说下如何区分TM标。 选择相对于的站点,选择TM。 软件采集出来的已备案、未备案TMR标,现在点击TM标就会跳到美国商标局。 可以清晰的看到这个地方只有一个序…

电力授时设备常用:低功耗定位授时模块ATGM332D-5T

ATGM332D有5N微星定位模块系列和5T授时模块,其中我们今天要解读的是一款拥有高性能、低功耗、低成本优势且适用于各类授时设备并支持BDS/GNSS的定位授时模块ATGM332D-5T。 该系列模块产品是基于中科微第四代低功耗GNSS SOC单芯片—AT6558,支持多种微星导…

【实战】EasyExcel实现百万级数据导入导出

文章目录 前言技术积累实战演示实现思路模拟代码测试结果 前言 最近接到一个百万级excel数据导入导出的需求,大概就是我们在进行公众号API群发的时候,需要支持500w以上的openid进行群发,并且可以提供发送openid数据的导出功能。可能有的同学…

《昇思25天学习打卡营第1天|基本介绍》

文章目录 前言:今日所学:昇思MindSpore相关链接: 前言: 今天非常荣幸的收到了昇思25天学习打卡营的邀请。昇思MindSpore作为华为昇腾AI全栈的重要一员,他支持端、边、云独立的和协同的统一训练和推理框架,…

电脑录歌用什么软件好?分享电脑录音软件:6款

短视频普遍的今天,越来越多的人喜欢通过电脑进行音乐创作和录制。然而,面对市面上琳琅满目的电脑录音软件,很多人可能会感到困惑:电脑录歌用什么软件好呢?本文将为大家分享六款精选的录音软件,帮助大家找到…

某网页gpt的JS逆向

原网页网址 (base64) 在线解码 aHR0cHM6Ly9jbGF1ZGUzLmZyZWUyZ3B0Lnh5ei8 逆向效果图 调用代码(复制即用) 把倒数第三行换成下面的base64解码 aHR0cHM6Ly9jbGF1ZGUzLmZyZWUyZ3B0Lnh5ei9hcGkvZ2VuZXJhdGU import hashlib import time import reques…

git提交实战

以新项目为例,如何在新项目新分支提交代码。 1.查看文件所在位置 git init 2.克隆项目到本地并完成身份配置 3.将需要新增的文件放到指定目录路径下 4.进入新克隆的文件 cd XXX 5.切换分支 git checkout XXX 6.标红者即为新提交的文件 git status 7.加入 git …

AI图生视频工具测试

环境: 即梦 pika LUMA 可灵 问题描述: AI图生视频工具测试下面是原图 解决方案: 1.即梦 效果 2.pika 生成效果 3.LUMA 生成效果还行 4.可灵 生成效果最好

AI模特换装试衣软件定制服务公司

🌟 最强AI模特换装试衣模型训练、定制服务公司出炉 —— 触站AI🚀 🎨 在AI技术的浪潮中,触站AI以其专业和创新,成为企业AI图像领域的技术解决方案服务公司,为设计界带来了革命性的变化。 🛠️ …

Hadoop3:Yarn的Tool接口案例

一、需求 依然以wordcount案例为基础,进行开发 我们知道,用hadoop自带的example.jar执行wordcount 命令如下 hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount -D mapreduce.job.queuename…

线性代数--行列式1

本篇来自对线性代数第一篇的行列式的一个总结。 主要是行列式中有些关键点和注意事项,便于之后的考研复习使用。 首先,对于普通的二阶和三阶行列式,我们可以直接对其进行拆开,展开。 而对于n阶行列式 其行列式的值等于它的任意…