关于splay的一些说明

article/2025/11/9 15:48:52

    • 前言
    • splay出现的背景
    • 它的操作
      • push_up
      • rotate
      • splay
      • find
      • insert
      • next
      • delete
      • findkth
      • main

前言

暑假快过完了,大家感觉是不是很棒!gay房也体验过最新的断电模拟器了.
好吧,在开讲之前我还是得说一个.刀剑神域的外传实在太好看了!
除了精彩的战斗场面,神一般的人设和强大的剧情,比起前作来看又多了一样我最喜欢的东西.
神曲!我从一名听众到神崎艾莎的粉丝只经过了一首歌.
Independence.燃到甚至有点伤感.看过作品的人应该能懂歌曲中隐含的感情.
我是一边听这首歌一边写的这个博客,大家也可以一边听歌一边欢乐地享受这一讲的内容.
好了讲正事.终于要开始研究神奇的数据结构了.
至于讲哪个,题目已经说了,我也就不扯淡了.
其实splay还是非常好懂的.
至于线段树什么的我自己也是先贺了一遍代码然后写一遍博客,只要3个月不学自会.
现在离AFO也差不多3个月,大概是能够会的.

splay出现的背景

大家都知道二叉查找树有概率会退化成链,这样时间复杂度就会大大增高.
这个时候有个叫Robert Tarjan 的男人和另一个叫Daniel Sleator的人发明了这样的一种数据结构.
它在每次询问的时候可以将树旋转(也就是splay这个操作)来维护树的平衡性.

它的操作

我们就以模板[普通平衡树]为例来学习这种数据结构.
那么这种数据结构应该要支持插入删除,求第 k k 大,求排名,求前驱和后继.
首先我们定义struct splaytree.

const int yuzu=1e5;
typedef int fuko[yuzu|10];
/*事先准备maxsize和大小为maxsize的数组.*/
struct splaytree{
fuko ch[2],sz,cnt,fa,val;
/*ch是孩子,标号0,1分别表示左孩子和右孩子;sz是这个节点的子树大小(包括自己);cnt是这个节点保存的相同的值的个数(万一出现同样的数字只要cnt[u]++就可以了);fa是节点的父亲;val是节点保存的值.按照本题的要求来讲开这一些数组就够了.*/
然而我还要define一些东西.
#define ls(x) ch[0][x]
#define rs(x) ch[1][x]
#define ws(x,y) (rs(x)==y)
/*这个是which_son的简称,就是看y是x的左孩子还是右孩子.如果x的右孩子等于y,返回1.接下来会很骚,还请注意.*/
}my_;//开一棵名字叫做my_的splay树.当然要取名为其他东西也是可以的.

注:接下来为了巩固splay的知识,我都直接在代码框里手打代码,如果出现错误还望大家指出.
如果你也初学splay,贺代码请谨慎,出现错误,我不背这个锅.

push_up

void push_up(int u){sz[u]=sz[ls(u)]+sz[rs(u)]+cnt[u];/*这个操作解释不用很多,就是左孩子的子树大小+右孩子的子树大小+节点u包含相同字的个数.*/}

rotate

这个直接把代码放上来只有神仙能看懂.我该画图解释.
注意:二叉查找树满足左孩子权值小于节点权值,而右孩子权值严格大于节点的权值,以下图片中显现的是节点编号而不是节点权值.
这里写图片描述
看上图,我们现在要把节点4旋转到 2 2 的位置,为了使它仍然满足二叉查找树的性质,我们来操作一下.
旋转完的树应该是这样的.
这里写图片描述
给每个节点代个权值进去会发现它仍然满足二叉树的性质.
我们不妨来找旋转的规律.

首先假设要旋转的节点是x,它的父亲是y,祖父是z.
我们先找出x与y的关系(左右儿子).
然后必然满足的条件:
1.x的x相对于y的子树不变.
2.x的相对于x相对于y的子树变成了y的x相对于y方向的子树.(仔细思考)
3.x的父亲变成z,y变成相对于x原来相对于y的儿子.

接下来写出旋转的代码,可结合上图分析使用.

void zhuan(int x){//rotateint y=fa[x],z=fa[y],k=ws(y,x);//取父亲和祖父,求x是y的哪一个儿子.ch[ws(z,y)][z]=x,fa[x]=z;/*z的y原来相对于z的儿子变成x,x的父亲变成z.*/ch[k][y]=ch[k^1][x],fa[ch[k^1][x]]=y;/*x的与原来x相对于y的方向相反的儿子变成y的x相对于y方向的儿子,同理赋值父亲.*/ch[k^1][x]=y,fa[y]=x;/*y变成x的与原来x相对于y相反方向的儿子,y的父亲变成x.*/push_up(y),push_up(x);/*push_up一下.*/}

给你们10分钟时间听歌思考.
……

splay

该树最核心的操作之一────splay操作出现了.
如果你能够理解上面的旋转操作我们就直接上代码了.

void splay(int x,int g){//将x旋转成为g的儿子,如果g是0则将x旋转到根. for (;fa[x]^g;zhuan(x)){//最后一定会旋转一次x int y=fa[x],z=fa[y];if (z^g) zhuan(ws(z,y)^ws(y,x)?x:y);//如果z不是根就额外转一次./*通过判断y是z的哪个儿子和x是y的哪个儿子的关系决定是旋转y还是x.其实分类讨论zig,zag也是这样的算法,非常麻烦,用这个就很简单了.*/ }if (!g) rt=x;//如果g等于0将x设为根. }
/*这些操作的意图都是使树变得平衡.*/

find

把维护splay平衡的代码写完了,终于到询问的时候了.
这个操作能够找到存放大小为x的数字的那个节点,并把这个节点splay到根.

void find(int x){int u=rt;if (!u) return;//rt为0,该树中没有点,直接跳出.for (;ch[x>val[u]][u]&&val[u]^x;)u=ch[x>val[u]][u];/*根据二叉查找树的性质,根据x关于左右儿子存放的值的关系在左右儿子中查找.注意特判x恰好和u存放的值相等的情况.*/splay(u,0);//将u节点splay到根,为求x是第几大的数字做铺垫.}

insert

插入一个节点.

void insert(int x){int u=rt,nf=0;//nf是现在u的父亲.for (;u&&val[u]^x;) nf=u,u=ch[x>val[u]][u];//按顺序找x应该在的位置.if (u) ++cnt[u];//如果已经存在x,将cnt++.else{//不存在,新开一个节点.u=++tot;if (nf) ch[x>val[nf]][nf]=u;ls(u)=rs(u)=0;fa[u]=nf,val[u]=x,cnt[u]=1,sz[u]=1;}splay(u,0);//不要忘记splay到根,保持树的平衡性.}

next

前驱和后继.

int next(int x,int f){//f前驱为0,后继为1find(x);int u=rt;if (val[u]>x&&f||val[u]<x&&!f) return u;/*find过后val[u]如果不等于x,它一定是最接近x的那个数,如果此时val[u]比x大或者小满足要求的前或者后,就可以直接判断前驱后继.*/u=ch[f][u];//先往u向f的那个方向跳一个儿子.for (;ch[f^1][u];) u=ch[f^1][u];/*接下来一直往反方向跳,就可以得到比x大的最小的数或者比x小的最大的数.*/return u;}

delete

借助next可以完成删除操作.

void del(int x){int lat=next(x,0),net=next(x,1);/*定义last为x的前驱,next为x的后继.*/splay(lat,0),splay(net,lat);/*把last转到根,next转到last的右儿子.显然一定是转到右儿子.*/int nde=ls(net);//那么值为x的节点此时应该是next的左儿子.if (cnt[nde]>1){//cnt[nde]>1的话可以只是删掉一个.cnt[nde]--;splay(nde,0);//转到根节点.}else ls(net)=0; }

findkth

最后是找第k大值.

int kth(int x){int u=rt;if (sz[u]<x) return 0;/*如果sz[u]<x说明整棵树里没有k个值,也当然没有第k大.*/for (;;){int y=ls(u);if (x>sz[y]+cnt[u]){x-=sz[y]+cnt[u];u=rs(u); }else if (sz[y]>=x) u=y;else return val[u];}//这一部分分类讨论估计也不用讲了.}

main

main就很简单了.

int main(){
my_.insert(-1e7-4);
my_.insert(1e7+4);
n=read();
for (;n--;){int op=read();switch(op){case 1: my_.insert(read()); break;case 2: my_.del(read()); break;case 3: my_.find(read()),write(my_.sz[my_.ls(rt)]),pl; break;case 4: write(my_.kth(read()+1)),pl; break;case 5: write(my_.val[my_.next(read(),0)]),pl; break;case 6: write(my_.val[my_.next(read(),1)]),pl; break;}}
}

预告:接下来会写LCT.
谢谢大家.


http://chatgpt.dhexx.cn/article/R38BdcS6.shtml

相关文章

Splay入门详解

Splay入门详解 写在前面 听说平衡树是一种强大的数据结构&#xff0c;听同年级或高年级大佬们讲起来也感觉很牛笔的亚子&#xff0c;而最近XC又叫我们去学习一下 L C T LCT LCT&#xff01;&#xff1f; 又因为 S p l a y Splay Splay是学习 L C T LCT LCT的基础&#xff0c;…

【学习笔记】Splay

普通平衡树 模板题链接 1、引入 一种二叉树&#xff0c;这棵树满足任意一个节点&#xff0c;它的左儿子的权值<自己的权值<右儿子的权值 这种树叫做二叉查找树&#xff0c;这个概念应该在初赛中见过了吧 Splay就是利用这个结构来实现的 2、变量 模板题的7大变量 s…

平衡树详解(Splay)

引入 先看例题&#xff1a;&#xff08;洛谷 P3369 【模板】普通平衡树&#xff09; 您需要写一种数据结构&#xff0c;来维护一些数&#xff0c;其中需要提供以下操作&#xff1a; 1.插入 x x x 数 2.删除 x x x 数(若有多个相同的数&#xff0c;因只删除一个) 3.查询 x x…

Splay入门解析【保证让你看不懂(滑稽)】

upd&#xff1a;忽然发现直接在百度上搜索 splay s p l a y 就会搜索到我这篇文章&#xff1f;小蒟蒻瑟瑟发抖。 因为CSDN这边我基本不会看回复什么的&#xff0c;所以如果有问题还是请移步cnblogs里面吧 Cnblogs里面这篇文章的链接 BST真是神奇的东西。。。 而且种类好多呀…

Splay Tree

类别&#xff1a;二叉排序树 空间效率&#xff1a;O(n) 时间效率&#xff1a;O(log n)内完成插入、查找、删除操作 创造者&#xff1a;Daniel Sleator和Robert Tarjan 优点&#xff1a;每次查询会调整树的结构&#xff0c;使被查询频率高的条目更靠近树根。 注&#xff1a;所有…

【算法详解】splay的初步了解

qwq表示最近感受到了不停课的鸭梨啊&#xff0c;好难搞啊……算法太难学了……我好菜啊qwq 其实半个多月前就可以写这篇文章&#xff0c;不过由于时间紧张没写emmmmmm&#xff0c;不扯闲言乱语了&#xff0c;如果有什么写的不好的地方凑活一下吧2333333333 splay是一种可以使树…

splay详解

一&#xff1a;什么是splay&#xff08;伸展树&#xff09; 首先了解二叉排序树 二叉排序树或者是一棵空树&#xff0c;或者是具有下列性质的 二叉树&#xff1a; &#xff08;1&#xff09;若左子树不空&#xff0c;则左子树上所有结点的值均小于或等于它的 根结点的值&#…

Splay 总结基础精华

前言&#xff1a; 第一次学习Splay是2月份&#xff0c;打板子的时候是3月份&#xff0c;Ac是4月份&#xff0c;写这篇博客是6月初&#xff1b;原因是因为我竟然发现我忘了Splay的板子了&#xff01;很慌&#xff0c;必须总结一下&#xff01; 不敢说是最详细的&#xff0c;但希…

关系型数据库与非关系型数据库详解

关系数据库与非关系型数据库 一、数据库概述1、关系型数据库2、非关系型数据库 二、数据库区别1、数据存储方式不同2、扩展方式不同3、对事务性的支持不同 三、非关系型数据库产生背景四、Redis简介1、Redis 优点 五、Redis 安装部署六、Redis 命令工具&#xff08;1&#xff0…

关系型数据库RDS基本简介

OSS存放非结构化的数据&#xff0c;如&#xff1a;音频、视频、图片 rds存放结构化的数据&#xff0c;如&#xff1a;一张表&#xff0c; RDS产品概要 可以根据业务的需求进行弹性伸缩。 采用主从备份架构&#xff0c;三节点数据存储&#xff0c;具备高可用性和数据可靠性 …

2 关系型数据库是什么?

目录结构 关系型数据库基本概念结构化查询语言数据定义语言&#xff08;DDL&#xff09;数据查询语言&#xff08;Data Query Language, DQL&#xff09;数据操纵语言&#xff08;Data Manipulation Language, DML&#xff09;数据控制语言&#xff08;Data Control Language, …

常见的关系型数据库有哪些

Oracle Database&#xff1a;由Oracle公司开发和维护的商业关系型数据库&#xff0c;具有广泛的应用场景和功能。 MySQL&#xff1a;开源关系型数据库&#xff0c;常用于Web应用程序和小型企业应用。 Microsoft SQL Server&#xff1a;由Microsoft公司开发和维护的商业关系型…

Bezier曲线快速相交计算(含代码)

Bezier曲线快速相交计算 背景介绍算法思路解释和分析示例参考资料 背景介绍 很多时候&#xff0c;需要计算曲线段与曲线段是否有交点。常规的思路是直接联立方程求解。不过&#xff0c;直接求方程的解这种思路通常在计算上开销较大。 针对任意曲线&#xff0c;曲线的方程阶次…

js绘制贝塞尔曲线(Bézier-Curve)

贝塞尔曲线(Bzier curve)&#xff0c;又称贝兹曲线或贝济埃曲线&#xff0c;是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线&#xff0c;贝兹曲线由线段与节点组成&#xff0c;节点是可拖动的支点&#xff0c;线段像可伸缩的皮筋&#xff0c;我们…

Bezier曲线与B-Spline曲线

微分几何基础 微分集合是用微分的方法来研究曲线的局部性质&#xff0c;如曲线的弯曲程度等。 一条可以用参数表示的三维曲线是一个有界点集&#xff0c;可以写成一个带参数的、连续的、单值的数学函数&#xff1a; { x x ( t ) y y ( t ) z z ( t ) 0 ⩽ t ⩽ 1 p p ( t…

初识贝塞尔(bezier)曲线

文章目录 资料援引贝塞尔曲线的用途一阶贝塞尔&#xff08;bezier&#xff09;曲线二阶贝塞尔&#xff08;bezier&#xff09;曲线三阶贝塞尔&#xff08;bezier&#xff09;曲线高阶贝塞尔&#xff08;bezier&#xff09;曲线三阶贝塞尔曲线求插值&#xff08;Slerp&#xff0…

贝塞尔曲线(Bezier Curve)原理、公式推导及matlab代码实现

1. 定义 贝塞尔曲线(Bezier curve)&#xff0c;又称贝兹曲线或贝济埃曲线&#xff0c;是应用于二维图形应用程序的数学曲线。一般的矢量图形软件通过它来精确画出曲线&#xff0c;贝兹曲线由线段与节点组成&#xff0c;节点是可拖动的支点&#xff0c;线段像可伸缩的皮筋&#…

Bezier(贝塞尔曲线通用规律算法-DEMO)

之前也看过一些相关贝塞尔曲线的知识&#xff0c;但就是一直没有实践应用&#xff1b; 今天&#xff0c;听到有同事&#xff1a;程序、美术&#xff0c;在讨论相关的&#xff0c;人物的曲线路径行走的问题&#xff1b; 一些数学比较牛X的&#xff0c;说了用2阶&#xff0c;或…

bezier曲线原理(简单阐述)

原理和简单推导&#xff08;以三阶为例&#xff09;&#xff1a; 设P0、P02、P2是一条抛物线上顺序三个不同的点。过P0和P2点的两切线交于P1点&#xff0c;在P02点的切线交P0P1和P2P1于P01和P11&#xff0c;则如下比例成立&#xff1a; 这是所谓抛物线的三切线定理。 当P0&…

Bezier贝塞尔曲线

1.简介 Bezier曲线在图形学和游戏中经常使用&#xff0c;一般用的比较多的是4个控制点的贝塞尔曲线&#xff0c;这里手写了一个仅供参考&#xff08;注&#xff1a;理论上也可以写任意多个点&#xff08;3个及以上&#xff09;的贝塞尔&#xff0c;就是一个递归的过程&#xff…