Splay入门讲解

article/2025/11/9 15:57:14

先安利一发,Splay入门解析【保证让你看不懂(滑稽)】
我就是看这个看懂的,这博客非常适合像我这种连平衡树都不知道是啥的蒟蒻。
另外,讲得不好还请见谅。


首先我们来看看平衡树是啥

平衡树首先是棵二叉搜索树,那么问题来了,二叉搜索树又是啥?

它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树----------百度百科

也就说,如图,二叉树中任意节点的权值大于左子树中的权值,小于右子树中的权值。
这里写图片描述
而平衡树,顾名思义,就是比较平衡,左右树的深度相差不大
但是,如何维护平衡树,使得它的深度较小呢?于是就有个叫Tarjan的大爷和另一个叫Sleator的大爷搞出来了一个splay


然后我们来看看splay的功能和时效

splay可以删除一个元素,插入一个元素,询问某元素的前驱或后继(前驱是指小于某元素的最大的元素,后继与之类似),将区间翻转,询问第k大的元素等等。
它的均摊复杂度是O(logN)的。
为什么说均摊复杂度呢,是因为splay这种东西不稳定,你如果按元素大小从小到大读入,它构成的树会退化成一条链。
像这样
这里写图片描述
这时候查询的时效是O(N)的,但通过splay操作可以让树的节构趋向于平衡。


接着我们来看看splay的基本操作的

首先,作为一棵平衡树,想要提高效率就要维护树的稳定。为此,我们引入rotate操作,改变节点的顺序。
在这之前,我们先定义son[x][k]表示节点x的子节点,k=0时,表示左儿子,k=1时表示右儿子。fa[x]表示节点x的父节点。

如图
这里写图片描述
我们要使x旋转为z的子节点,这势必要使y变成x的子节点,但是x已经有子节点,怎么办?
很简单,把x的某个儿子过继给y不就好啦。
于是树就变成了这样。

这里写图片描述

然后我们发现,树的性质并没有遭到破坏,说明旋转是可行的。
注意:被“过继”的儿子一点是x与y之间的儿子(看图理解一下)。
那到底发生了什么?
我们按从z到x的顺序来看。
son[z][0]由y变为x,fa[x]变为z。
son[y][0]由x变为绿色长方形,fa[绿色长方形]变为y。
son[x][1]由绿色长方形变为y,fa[y]变为x。
是不是看“绿色长方形”很不爽,我们尝试把它换掉。
设x为y的k儿子。0表示左,1表示右。
那绿色长方形就可以表示为son[x][k^1](理性理解一下)。
所以,rotate的函数可以这么写。

// cnt[x]是节点x的元素的数量,size[x]是x的子树的cnt之和。后面会用到
inline void pushup(int x) {size[x]=size[son[x][0]]+size[son[x][1]]+cnt[x];}
inline void rotate(int x) {int y=fa[x],z=fa[y],k=son[y][1]==x;if (z) son[z][0]=x;son[y][0]=son[x][k^1],fa[son[x][k^1]]=y;son[x][k^1]=y,fa[y]=x,fa[x]=z;pushup(y),pushup(x);
}

这是旋转一个节点,那我们要将一个节点旋转到某节点的下方该怎么做?
我们自然而然就想到不断地将节点旋转旋转再旋转。
于是代码就是这样的:

inline void Splay(int x,int goal) {if (goal==0) root=x;while (fa[x]^goal) rotate(x);
}

然后你就会愉快地TLE了(也有可能水过去)。

为什么?

我们来看这种情况。

这里写图片描述

将2旋转到根。
按照代码操作,结果是这样的:

这里写图片描述

经过操作后,树的深度还是6,没有得到改善。假如每次询问最低的节点,询问复杂度都是O(N)的。
这就很尴尬。

那怎么解决这个问题?
我们就引入一个骚操作(双旋):
当前节点为x,y为x的父节点,z为y的父节点。
x是y的k儿子,如果y也是z的k儿子,那我们就先旋转y,再旋转x。

于是就成了这样:

这里写图片描述

我们发现,树的深度减小了1。
但你肯定会说,这有什么卵用,才减小了一层,凭什么为这样就不会TLE?

我们来观察一下,树深度减小,是因为节点3与节点5处在了同一层,而之前节点5应该是节点3的爷爷。

为什么会这样?分析双旋的操作:

先旋转父节点,使爷爷节点和自己都成为父节点的孩子,此时父节点和自己处在同一层,原来的爷爷节点成为自己的父节点。

然后旋转自己,自己旋转方向的孩子节点成为自己现在父节点(原先爷爷节点)的孩子,也就是和自己原先父节点处在了同一层。
此处参考浅谈splay的双旋

这样就加快了splay的效率,使得均摊复杂度是O(logN)
所以代码就成了这样:

inline void Splay(int x,int goal) {if (goal==0) root=x;while (fa[x]^goal) {int y=fa[x],z=fa[y];if (z^goal) (son[y][1]==x)^(son[z][1]==y)?rotate(x):rotate(y);// 给自己提个醒,(z^goal)不要写成(z) QwQrotate(x);}
}

我们再来看看splay的一些操作

我们先定义:
cnt[x]是节点x的元素的数量。
size[x]是x的子树的cnt之和。
val[x]是x的权值。

插入(Insert)
类似二分查找,比当前节点大搜索右子树,否则搜索左子树。
搜到相等的元素,元素个数直接加一,否则在开一个点。

inline void Insert(int x) {int u=root,ff=0;while (u&&val[u]^x) ff=u,u=son[u][x>val[u]];if (u) cnt[u]++;else {u=++tot;if (ff) son[ff][x>val[ff]]=u;val[u]=x,cnt[u]=1,size[u]=1;fa[u]=ff,son[u][0]=son[u][1]=0;}Splay(u,0);
}

查找元素x并把它splay到根(Find)
如果没有找到,会将一个权值与x相近的节点splay到根。

inline void Find(int x) {int u=root;if (!u) return;while (son[u][x>val[u]]&&val[u]^x) u=son[u][x>val[u]];Splay(u,0);
}

寻找元素x的前驱或后继(Next)

inline int Next(int x,bool f) {
// f=0时为求前驱,f=1时求后继Find(x);if (val[root]<x&&!f) return root;if (val[root]>x&& f) return root;int u=son[root][f];while (son[u][f^1]) u=son[u][f^1];return u;
}

删除(Delete)
找到元素x的前驱,把它Splay到根,找到x的后继,Splay到根的下方,你会发现,后继的左儿子就是x,且x无儿子,把它删除后不会对其他节点造成影响。

inline void Delete(int x) {int next=Next(x,1),last=Next(x,0);Splay(last,0),Splay(next,last);int u=son[next][0];if (--cnt[u]==0) son[next][0]=0,size[fa[next]]--;else Splay(u,0);
}

求第k大(K_th)

inline int K_th(int k) {int u=root;while (1) {int v=son[u][0];if (size[v]+cnt[u]<k)k-=size[v]+cnt[u],u=son[u][1];elseif (size[v]>=k) u=v;else return val[u];}
}

模板题普通平衡树
AC代码:

#include<cstdio>
#include<string>
using namespace std;
const int maxn=1e5+6;
int N,opt,x,tot,root;
int cnt[maxn],size[maxn],fa[maxn],son[maxn][2],val[maxn];
inline int read() {int ret=0,f=1;char ch=getchar();for (; !isdigit(ch); ch=getchar()) if (ch=='-') f=-f;for (; isdigit(ch); ch=getchar()) ret=ret*10+ch-48;return ret*f;
}
inline void push_up(int x) {size[x]=size[son[x][0]]+size[son[x][1]]+cnt[x];}
inline void rotate(int x) {int y=fa[x],z=fa[y],k=son[y][1]==x;if (z) son[z][son[z][1]==y]=x;fa[x]=z;son[y][k]=son[x][k^1],fa[son[x][k^1]]=y;son[x][k^1]=y,fa[y]=x;push_up(y),push_up(x);
}
inline void Splay(int x,int goal) {if (goal==0) root=x;while (fa[x]^goal) {int y=fa[x],z=fa[y];if (z^goal) (son[y][1]==x)^(son[z][1]==y)?rotate(x):rotate(y);rotate(x);}
}
inline void Find(int x) {int u=root;if (!u) return;while (son[u][x>val[u]]&&val[u]^x) u=son[u][x>val[u]];Splay(u,0);
}
inline int Next(int x,bool f) {Find(x);if (val[root]<x&&!f) return root;if (val[root]>x&& f) return root;int u=son[root][f];while (son[u][f^1]) u=son[u][f^1];return u;
}
inline void Insert(int x) {int u=root,ff=0;while (u&&val[u]^x) ff=u,u=son[u][x>val[u]];if (u) cnt[u]++;else {u=++tot;if (ff) son[ff][x>val[ff]]=u;val[u]=x,cnt[u]=1,size[u]=1;fa[u]=ff,son[u][0]=son[u][1]=0;}Splay(u,0);
}
inline void Delete(int x) {int next=Next(x,1),last=Next(x,0);Splay(last,0),Splay(next,last);int u=son[next][0];if (--cnt[u]==0) son[next][0]=0,size[fa[next]]--;else Splay(u,0);
}
inline int K_th(int k) {int u=root;while (1) {int v=son[u][0];if (size[v]+cnt[u]<k)k-=size[v]+cnt[u],u=son[u][1];elseif (size[v]>=k) u=v;else return val[u];}
}
int main() {N=read(),tot=0;Insert(-2147483647);Insert(+2147483647);for (int i=1; i<=N; i++) {opt=read(),x=read();if (opt==1) Insert(x);elseif (opt==2) Delete(x);elseif (opt==3) Find(x),printf("%d\n",size[son[root][0]]);elseif (opt==4) printf("%d\n",K_th(x+1));elseprintf("%d\n",val[Next(x,opt==6)]);}return 0;
}

zyc要求讲一讲splay的区间操作,但我还不太理解。
就不献丑了,安利一波dalao的讲解。
伸展树的学习(六):伸展树的区间操作(区间翻转,旋转,增加一个数,求最小值)
NOI2005维护序列题解


http://chatgpt.dhexx.cn/article/8ttnkYa4.shtml

相关文章

史上第二详尽的平衡树(Splay)详解

谢鸣&#xff1a;本文来自zyf2000学姐的blog&#xff0c;原题为“史上最详尽的平衡树(splay)讲解与模板”&#xff0c;我在这里拿过来使用&#xff0c;命名为“史上第二详尽的平衡树&#xff08;Splay&#xff09;详解”&#xff0c;并加上了一些新的操作. 变量声明&#xff1…

splay复习小记

简介 splay的原名是伸展树&#xff0c;一种超级实用的数据结构&#xff0c;能快速地干很多数据结构不能干的事情。 很久以前就听说过并且略微地学了一些&#xff0c;但是当时了解地并不是很多。 有些人把splay达成spaly叫做死吧你&#xff0c;(⊙﹏⊙)b 结构 实质上他是一个…

Splay学习笔记

昨天这个时候到现在终于把Splay给搞明白了&#xff0c;还A了一道郁闷的出纳员&#xff1b;刚学完的感受&#xff1a;我再也不碰这东西了&#xff1b;做完郁闷的出纳员的感受&#xff1a;我发誓这辈子不当出纳员(虽然这确实只是个入门题……) 于是来讲一讲这个恶心的东西吧………

[Splay伸展树]splay树入门级教程

首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角。。 PS:若代码有误,请尽快与本人联系,我会尽快改正 首先引入一下splay的概念,他的中文名是伸展树,意思差不多就是可以随意翻转的二叉树 PS:百度百科中伸展树读作:BoGang,不知道是不是因为和某位大牛…

Splay Tree(伸展树)

二叉查找树&#xff08;Binary Search Tree&#xff09;能够支持多种动态集合操作。因此&#xff0c;在信息学竞赛中&#xff0c;二叉排序树起着非常重要的作用&#xff0c;它可以被用来表示有序集合、建立索引或优先队列等。 作用于二叉查找树上的基本操作的时间是与树的高度…

浅谈splay

例题&#xff1a; 给出一个长度为n序列a。 有m次操作&#xff0c;每次操作可以修改a[i]&#xff0c;在第i个数前插入一个数x&#xff0c;或查询区间[l,r]的最大值。 1≤n≤100000,1≤m≤100000。 强制在线 看到这道题最自然的反应就是用线段树&#xff0c;但有了插入操作线段树…

关于splay的一些说明

前言splay出现的背景它的操作 push_uprotatesplayfindinsertnextdeletefindkthmain 前言 暑假快过完了,大家感觉是不是很棒!gay房也体验过最新的断电模拟器了. 好吧,在开讲之前我还是得说一个.刀剑神域的外传实在太好看了! 除了精彩的战斗场面,神一般的人设和强大的剧情,比…

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;曲线的方程阶次…