[学习笔记] Splay & Treap 平衡树 - 数据结构
[学习笔记] Splay & Treap 平衡树 - 数据结构
Splay 树
又名伸展树,一种平衡二叉查找树,通过 操作不断把节点旋到根节点来维护整颗树的平衡。
说人话,很玄学的玩意,复杂度是单 log 级别的。为啥是单 log,科学的解释请移步 OI-WIKI。不科学的解释就是,通过不断 ,以至于整棵树不会变成一条链,再加上二叉搜索树的看家本领,有点类似于二分查找,于是复杂度就变成 了。
结构体定义
因为 Splay树 本质就是棵二叉查找树,所以维护的东西和二叉树一样。
struct{ int fa, ch[2], siz, cnt, val, tag; }; int tot, rt;
fa
: 表示当前节点的父亲节点编号。ch[2]
: 表示当前节点的左右儿子编号。特殊地,左儿子的权值一定小于当前节点的权值,右儿子的权值一定大于当前节点的权值。siz
: 表示当前节点的子树大小。用于找第 最值。cnt
: 表示有多少和当前节点权值一样的节点。就是把权值一样的点并到一起。val
: 当前节点的权值。tot
: 记录点的编号。类似于动态开点,rt
: Splay树的根节点标号。tag
: 如果涉及区间修改,那么还要维护懒标记。
基本操作
inline bool get(int x){ return x == t[t[x].fa].ch[1]; }
用于找到该节点是父节点的哪一个儿子
inline void update(int x){ t[x].siz = t[t[x].ch[0]].siz + t[t[x].ch[1]].siz + t[x].cnt; }
更新(统计)子树大小。类比于线段树的 upshup
操作,也可以维护区间和之类的东西。
Splay 操作
单次 splay 的本质就是,把某个节点与它父亲的关系调换一下。因为这是个二叉查找树,所以这种操作很方便:看图:

假设 的权值比 大,那么旋转过来之后 就要变成 的右儿子。总的来说,就是父子关系调换,左右儿子关系调换。这是最基本的操作。
如果加上祖先节点,那么情况会有所不同:
通俗来讲,就是当父亲和祖先节点不共线的时候,先旋转 ,再旋转 。当父亲和祖先结点共线的时候,先旋转 ,再旋转 。有个口诀:折转底,直转中。
为什么要这么操作呢?如果不这么做会大幅增加你的时间复杂度,就是会 TLE。因为这么做可以最大限度度地保证树的平衡。
定义 splay(x, king)
表示把 节点旋转到 的儿子节点。特殊地,当 的时候,相当于把 旋转为根。
inline void splay(int x, int king){ //x 要旋转的节点 king 目标节点 while(t[x].fa != king){ int y = t[x].fa, z = t[y].fa; // 判断2 3情况 if(z != king) (get(x) ^ getr(y)) ? rotate(x) : rotate(y); // 无论哪一种情况,都要旋 x rotate(x); } // 如果king == 0 那么就代表把 x 旋为根节点 if(king == 0) rt = x; }
rotate 操作
rotate(x)
表示将 向上旋转一次。对于初学者来说,这个是最不好写的操作了。如果你忘了,不妨拿张演草纸手玩一下,按照下面的图,从上到下依次修改即可保证正确。注意一些节点的父子关系会发生变化。
inline void rotate(int x){ int y = t[x].fa, z = t[y].fa; bool k = get(x); t[z].ch[get(y)] = x; t[x].fa = z; t[t[x].ch[k ^ 1]].fa = y; t[y].ch[k] = t[x].ch[k ^ 1]; t[x].ch[k ^ 1] = y; t[y].fa = x; update(y), update(x); //先更新儿子 }
插入操作
先按照二叉搜索树的方式去找这个节点,如果找不到,那么新开节点,如果这个节点本身就存在,那么 即可。最后都要把这个节点旋到根。
inline void insert(int x){ int u = rt, fa = 0; while(u && x != t[u].val) fa = u, u = t[u].ch[x > t[u].val]; if(u) ++t[u].cnt; else{ u = ++cnt; if(fa) t[fa].ch[x > t[fa].val] = u; t[u].val = x, t[u].fa = fa, t[u].siz = t[y].cnt = 1; } splay(u, 0); }
find 操作
找到权值 在树里对应的编号,并将它旋转到根。如果不存在,那么旋转与它权值最为相近的节点。
inline void find(x){ int u = rt; while(t[u].ch[x > t[u].val] && x != t[u].val) u = t[u].ch[u > t[u].val]; splay(u, 0); }
删除操作
找到权值在
本文作者:XiaoLe_MC
本文链接:https://www.cnblogs.com/xiaolemc/p/18383678
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步