平衡树专题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; //树中不存在这个数 那就是目前查到比它小的数的数目+1if (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先旋到根节点 而且一定要插入而不是直接旋 防止树上本来就没有xprintf("%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 也是 如果该更新的没更新肯定就寄了
但是把没必要更新的也更新了问题就不大
所以实在不行这俩就是能写就写

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

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

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

相关文章

为适配kubelet:v0.4 安装指定版本的docker

系统版本信息 cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) 0.4 版本的kubelet 报错信息记录 E0603 19:00:38.273720 44142 kubelet.go:734] Error syncing pod: API error (400): {"message": "starting container with non-empty reque…

免交互简单操作

免交互 交互:我们发出指令控制程序的运行,程序在接收到指令后按照指令的效果作出对应的反应 免交互:间接的,通过第三方的方式把指令传给程序,不用直接下达指令 Here Document免交互 这是命令行格式,也可…

不用找了!这个软件自带各行业话术,客服效率飞跃

有一款客服工具软件,不但能吸附聊天窗口,实现图文视频话术的一键发送,还内置了多行业的优质客服话术模板,允许用户直接下载使用,快速构建起适合自身企业的专业客服知识库。 前言 在今天的快节奏商业环境中&#xff0c…

Linux shell脚本编程

一、sehll简介: 用户通过shell向计算机发送指令的 计算机通过shell给用户返回指令的执行结果 1.1、通过shell编程可以达到的效果 提高工作的效率 可以实现自动化 1.2、sehll脚本编写的流程 1、用vi/vim创建一个.sh的文件 2、在文件中进行开发 3、个文件赋予可执行权…

CesiumJS【Basic】- #047 绘制闪烁线(Entity方式)- 需要自定义着色器

文章目录 绘制闪烁线(Entity方式)- 需要自定义着色器1 目标2 代码2.1 main.ts绘制闪烁线(Entity方式)- 需要自定义着色器 1 目标 使用Entity方式绘制闪烁线 2 代码 2.1 main.ts import * as Cesium from cesium;const viewer = new Cesium<

【如何使用RSA签名验签】python语言

文章目录 签名方法异步同步通知数据验签生活号响应数据验签同步响应数据验签 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留的…

作业7.2

用结构体数组以及函数完成: 录入你要增加的几个学生&#xff0c;之后输出所有的学生信息 删除你要删除的第几个学生&#xff0c;并打印所有的学生信息 修改你要修改的第几个学生&#xff0c;并打印所有的学生信息 查找你要查找的第几个学生&#xff0c;并打印该的学生信息 1 /*…

idea常用问题记录

文章目录 1.ant构建报错编译错误1.1 解决办法 1.ant构建报错编译错误 Compile failed;xxx 1.1 解决办法

Python系统教程02

巩固 input()输出函数 回顾 1 、 input()函数&#xff1a; 在 input()函数输入时&#xff0c;输入的内容一定为字符串类型。 2 、条件分支语句&#xff1a; 每一个 if 语句可以看成一个个体&#xff0c;elif 和 else 都是一个 if 个体的一部分&#xff0c;每一个 if 个体 运…

51单片机外部中断(按键识别)

欢迎入群共同学习交流 时间记录&#xff1a;2024/7/2 一、电路原理图 51单片机包含INT0、INT1两个外部中断接口 二、知识点介绍 1.中断寄存器位介绍 &#xff08;1&#xff09;TCON定时控制寄存器&#xff0c;位0&#xff08;IT0&#xff09;中断INT0请求信号选择位&#x…

WordPress主题开发进群付费主题v1.1.2 多种引流方式

全新前端UI界面&#xff0c;多种前端交互特效让页面不再单调&#xff0c;进群页面群成员数&#xff0c;群成员头像名称&#xff0c;每次刷新页面随机更新不重复&#xff0c;最下面评论和点赞也是如此随机刷新不重复 进群页面简介&#xff0c;群聊名称&#xff0c;群内展示&…

注意!年龄越大,社交圈子越窄?其实这是老人的理性选择!数学家告诉你:何时该跳槽,何时该坚守!你必须知道的三个智慧:让你的人生更加精彩!

我们到底应该在什么情况下探索新事物&#xff0c;什么情况下专注于已有的东西呢&#xff1f;本质上来说&#xff0c;这个问题就是在询问&#xff0c;你究竟应该耗费精力去探索新的信息&#xff0c;还是专注从既有的信息中获取收获&#xff1f; 有人采访了临终的老人&#xff0c…

中国三大平原矢量示意图分享

我们在《中国地势三级阶梯示意图分享》、《中国四大高原矢量示意图分享》和《中国主要山脉矢量示意图分享》等文中&#xff0c;为你分享过中国地势相关的矢量示意图。 现在再为你分享一下我国东北平原、华北平原和长江中下游平原的矢量示意图&#xff0c;这三大平原均位于我国…

随想录总结 Day 77

随想录总结 Day 77 回忆75天的做题时间&#xff0c;差点没坚持下来的有两个时间点&#xff0c;一个是在前20天&#xff0c;很多时候二叉树这种基础题&#xff0c;前中后序列遍历之类的。基础&#xff0c;但真正写一遍&#xff0c;每道题又有多种写法。花了很长时间但是也就是一…

go sync包(七)Sync.Map

Sync.Map 原理 通过 read 和 dirty 两个字段实现数据的读写分离&#xff0c;读的数据存在只读字段 read 上&#xff0c;将最新写入的数据存在 dirty 字段上。读取时会先查询 read&#xff0c;不存在再查询 dirty&#xff0c;写入时则只写入 dirty。读取 read 并不需要加锁&am…

每天一个数据分析题(三百九十九)- 逻辑回归

逻辑回归中&#xff0c;若选0.5作为阈值区分正负样本&#xff0c;其决策平面是&#xff08; &#xff09; A. wxb&#xff1d; 0 B. wxb&#xff1d; 1 C. wxb&#xff1d; -1 D. wxb&#xff1d; 2 数据分析认证考试介绍&#xff1a;点击进入 题目来源于CDA模拟题库 点…

Python实现万花筒效果:创造炫目的动态图案

文章目录 引言准备工作前置条件 代码实现与解析导入必要的库初始化Pygame定义绘制万花筒图案的函数主循环 完整代码 引言 万花筒效果通过反射和旋转图案创造出美丽的对称图案。在这篇博客中&#xff0c;我们将使用Python来实现一个动态的万花筒效果。通过利用Pygame库&#xf…

大数据可视化实验(八):大数据可视化综合实训

目录 一、实验目的... 1 二、实验环境... 1 三、实验内容... 1 1&#xff09;Python纵向柱状图实训... 1 2&#xff09;Python水平柱状图实训... 3 3&#xff09;Python多数据并列柱状图实训.. 3 4&#xff09;Python折线图实训... 4 5&#xff09;Python直方图实训...…

PAT 1108 Finding Average

原题链接&#xff1a;PAT 1108 Finding Average The basic task is simple: given N real numbers, you are supposed to calculate their average. But what makes it complicated is that some of the input numbers might not be legal. A legal input is a real number in…

Python只读取Excel文件的一部分数据,比如特定范围的行和列?

如何只读取Excel文件的一部分数据&#xff0c;比如特定范围的行和列&#xff1f; 在Python中&#xff0c;如果你只想读取Excel文件的特定范围&#xff0c;可以使用以下方法&#xff1a; pandas: Pandas是一个强大的数据处理库&#xff0c;它有一个内置函数read_excel()用于读…