Splay 学习笔记

news/2024/9/19 18:03:05/文章来源:https://www.cnblogs.com/Kang-shifu/p/18308212

Splay 树, 或 伸展树,是一种平衡二叉查找树,它通过 Splay/伸展操作 不断将某个节点旋转到根节点,使得整棵树仍然满足二叉查找树的性质,能够在均摊 O(\log N) 时间内完成插入,查找和删除操作,并且保持平衡而不至于退化为链。

Splay 树由 Daniel Sleator 和 Robert Tarjan 于 1985 年发明。
code:

using namespace std;
const int maxn = 1e5+10;
int root,tot,fa[maxn],ch[maxn][2],val[maxn],cnt[maxn],sz[maxn];
int rt;
struct Splay{void maintain(int x){sz[x] = sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];//在改变节点位置后,将节点x的size更新 }bool get(int x){//判断x是父节点的左儿子还是右儿子 return x == ch[fa[x]][1];}void clear(int x){//销毁节点x ch[x][0] = ch[x][1] = fa[x] = val[x] = sz[x] = cnt[x] = 0;}/*将 y 的左儿子指向 x 的右儿子,且 x 的右儿子(如果 x 有右儿子的话)的父亲指向 y;ch[y][0]=ch[x][1]; fa[ch[x][1]]=y;将 x 的右儿子指向 y,且 y 的父亲指向 x;ch[x][chk^1]=y; fa[y]=x;若原来的 y 还有父亲 z,那么把 z 的某个儿子(原来 y 所在的儿子位置)指向 x,且 x 的父亲指向 z。fa[x]=z; if(z) ch[z][y==ch[z][1]]=x;*/ void rotate(int x){int y = fa[x],z = fa[y],chk = get(x);ch[y][chk] = ch[x][chk^1];if(ch[x][chk^1]) fa[ch[x][chk^1]] = y;ch[x][chk^1] = y;fa[y] = x;fa[x] = z;if(z) ch[z][y == ch[z][1]] = x;maintain(y);maintain(x); }/*Splay 操作规定:每访问一个节点 x 后都要强制将其旋转到根节点。Splay 操作即对 x 做一系列的 splay 步骤。每次对 x 做一次 splay 步骤,x 到根节点的距离都会更近。定义 p 为 x 的父节点。Splay 步骤有三种,具体分为六种情况:*/void splay(int x){for(int f = fa[x];f = fa[x],f;rotate(x)){if(fa[f]) rotate(get(x) == get(f)?f:x);}rt = x;}/*如果树空了,则直接插入根并退出。如果当前节点的权值等于 k 则增加当前节点的大小并更新节点和父亲的信息,将当前节点进行 Splay 操作。否则按照二叉查找树的性质向下找,找到空节点就插入即可(请不要忘记 Splay 操作)。*/void ins(int k){if(!rt){val[++tot] = k;cnt[tot]++;rt = tot;maintain(rt);return;}int cur = rt,f = 0;while(1){if(val[cur] == k){cnt[cur]++;maintain(cur);maintain(f);splay(cur);break;}f = cur;cur = ch[cur][val[cur]<k];if(!cur){val[++tot] = k;cnt[tot]++;fa[tot] = f;ch[f][val[f]<k] = tot;maintain(f);maintain(tot);splay(tot);break;}}}/*如果 x 比当前节点的权值小,向其左子树查找。如果 x 比当前节点的权值大,将答案加上左子树(size)和当前节点(cnt)的大小,向其右子树查找。如果 x 与当前节点的权值相同,将答案加 1 并返回。*/int rk(int k){int res = 0,cur = rt;while(1){if(k < val[cur]){cur = ch[cur][0];}else{res += sz[ch[cur][0]];if(!cur) return res+1;if(k == val[cur]){splay(cur);return res+1;}res += cnt[cur];cur = ch[cur][1];}}}/*如果左子树非空且剩余排名 k 不大于左子树的大小 size,那么向左子树查找。否则将 k 减去左子树的和根的大小。如果此时 k 的值小于等于 0,则返回根节点的权值,否则继续向右子树查找。*/int kth(int k) {int cur = rt;while (1) {if (ch[cur][0] && k <= sz[ch[cur][0]]) {cur = ch[cur][0];} else {k -= cnt[cur] + sz[ch[cur][0]];if (k <= 0) {splay(cur);return val[cur];}cur = ch[cur][1];}}}/*前驱定义为小于 x 的最大的数,那么查询前驱可以转化为:将 x 插入(此时 x 已经在根的位置了),前驱即为 x 的左子树中最右边的节点,最后将 x 删除即可。*/int pre(){int cur = ch[rt][0];if(!cur) return cur;while(ch[cur][1]) cur = ch[cur][1];splay(cur);return cur;}int nex(){//后继 int cur = ch[rt][1];if (!cur) return cur;while(ch[cur][0]) cur = ch[cur][0];splay(cur);return cur;}/*首先将 x 旋转到根的位置。如果 cnt[x]>1(有不止一个 x),那么将 cnt[x] 减 1 并退出。
否则,合并它的左右两棵子树即可。合并:
如果 x 和 y 其中之一或两者都为空树,直接返回不为空的那一棵树的根节点或空树。
否则将 x 树中的最大值 \operatorname{Splay} 到根,然后把它的右子树设置为 y 并
更新节点的信息,然后返回这个节点。 
*/void del(int k) {rk(k);if (cnt[rt] > 1) {cnt[rt]--;maintain(rt);return;}if (!ch[rt][0] && !ch[rt][1]) {clear(rt);rt = 0;return;}if (!ch[rt][0]) {int cur = rt;rt = ch[rt][1];fa[rt] = 0;clear(cur);return;}if (!ch[rt][1]) {int cur = rt;rt = ch[rt][0];fa[rt] = 0;clear(cur);return;}int cur = rt;int x = pre();fa[ch[cur][1]] = x;ch[x][1] = ch[cur][1];clear(cur);maintain(rt);}}tree;
int n;
int main(){cin>>n;while(n--){int opt,x;cin>>opt>>x;if (opt == 1)tree.ins(x);else if (opt == 2)tree.del(x);else if (opt == 3)printf("%d\n", tree.rk(x));else if (opt == 4)printf("%d\n", tree.kth(x));else if (opt == 5)tree.ins(x), printf("%d\n", val[tree.pre()]), tree.del(x);elsetree.ins(x), printf("%d\n", val[tree.nex()]), tree.del(x);}return 0;
}

旋转操作

为了使 Splay 保持平衡而进行旋转操作,旋转的本质是将某个节点上移一个位置。

旋转需要保证:

整棵 Splay 的中序遍历不变(不能破坏二叉查找树的性质)。
受影响的节点维护的信息依然正确有效。
root 必须指向旋转后的根节点。
在 Splay 中旋转分为两种:左旋和右旋。

过程
具体分析旋转步骤(假设需要旋转的节点为 x,其父亲为 y,以右旋为例)

将 y 的左儿子指向 x 的右儿子,且 x 的右儿子(如果 x 有右儿子的话)的父亲指向 y;ch[y][0]=ch[x][1]; fa[ch[x][1]]=y;
image

将 x 的右儿子指向 y,且 y 的父亲指向 x;ch[x][chk^1]=y; fa[y]=x;
如果原来的 y 还有父亲 z,那么把 z 的某个儿子(原来 y 所在的儿子位置)指向 x,且 x 的父亲指向 z。fa[x]=z; if(z) ch[z][y==ch[z][1]]=x;

Splay 操作

Splay 操作规定:每访问一个节点 x 后都要强制将其旋转到根节点。

Splay 操作即对 x 做一系列的 splay 步骤。每次对 x 做一次 splay 步骤,x 到根节点的距离都会更近。定义 p 为 x 的父节点。Splay 步骤有三种,具体分为六种情况:

zig: 在 p 是根节点时操作。Splay 树会根据 x 和 p 间的边旋转。zig 存在是用于处理奇偶校验问题,仅当 x 在 splay 操作开始时具有奇数深度时作为 splay 操作的最后一步执行。

splay-zig

image

即直接将 x 左旋或右旋(图 1, 2)

图 1
image

图 2
image

zig-zig: 在 p 不是根节点且 x 和 p 都是右侧子节点或都是左侧子节点时操作。下方例图显示了 x 和 p 都是左侧子节点时的情况。Splay 树首先按照连接 p 与其父节点 g 边旋转,然后按照连接 x 和 p 的边旋转。

splay-zig-zig

即首先将 g 左旋或右旋,然后将 x 右旋或左旋(图 3, 4)。
图 3
image
图 4
image

zig-zag: 在 p 不是根节点且 x 和 p 一个是右侧子节点一个是左侧子节点时操作。Splay 树首先按 p 和 x 之间的边旋转,然后按 x 和 g 新生成的结果边旋转。

splay-zig-zag

image

即将 x 先左旋再右旋、或先右旋再左旋(图 5, 6)。

图 5
image

图 6
image

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

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

相关文章

redis学习-12(实现分布式锁、消息队列、缓存一致性问题、单线程快的原因、跳跃表)

引用以下内容: redis实现分布式锁:Redis分布式锁-这一篇全了解(Redission实现分布式锁完美方案) Redis实现分布式锁的7种方案,及正确使用姿势! redis实现消息队列Redis 的学习教程(十)之使用 Redis 实现消息队列 缓存一致性问题 想要保证数据库和 Redis 缓存一致性,推荐…

数据的运算(上)

逻辑门电路多路选择器和三态门加法器 一位全加器并行进位加法器

idea开发工具配置git,连接到gitee远程仓库

1. 打开idea,Settings里找到如下位置,正常idea会自动找到git,test测试,显示版本号说明正常 2. 创建本地Git仓库,默认就是当前项目路径, 不要修改,直接创建 3. 创建后自动识别出待提交的文件,输入说明,提交,提交后让输入git名称和邮箱,设置并提交,提交成功。我这个项…

使用Apache POI 处理Miscrosoft Office各种格式文件

介绍 Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用 POI 在 Java 程序中对Miscrosoft Office各种文件进行读写操作。一般情况下,POI 都是用于操作 Excel 文件。Apache POI 的应用场景: ● 银行网银系统导出交易明细 ● 各种业务…

闲话 717 - LGV 引理的小应用

717这是我们的某一天的联考题目:\(n\le 500\)。 显然使用平面图完美匹配计数可以获得 \(O(n^6)\),但是有一种神秘的对路径的双射。当时我们都认为这是超级人类智慧,但是今天看书发现是书上的某个例的题的方法(有不同)。。 考虑对正六边形的菱形密铺方案数(上图)。可以等…

useHeadSafe:安全生成HTML头部元素

title: useHeadSafe:安全生成HTML头部元素 date: 2024/7/17 updated: 2024/7/17 author: cmdragon excerpt: 摘要:“useHeadSafe”是Vue.js组合函数,用于安全生成HTML头部元素,通过限制输入值格式避免XSS等安全风险,提供了安全值白名单确保只有安全属性被添加。 categor…

Goby漏洞发布 | CVE-2024-4879 ServiceNowUI /login.do Jelly模板注入漏洞【已复现】

漏洞名称:ServiceNowUI /login.do Jelly模板注入漏洞(CVE-2024-4879) English Name:ServiceNowUI /login.do Input Validation Vulnerability(CVE-2024-4879) CVSS core: 9.3 漏洞描述: ServiceNow 是一个业务转型平台。通过平台上的各个模块,ServiceNow 可用于从人力资…

EMQX配置用户名和密码开启emqx_auth_mnesia认证方式连接

1、 找到MQtt 的 /etc/plugins/ 文件夹下的emqx_auth_mnesia.conf 文件 vim打开编辑该文件,根据例子添加账号密码 并保存 添加内容: auth.user.1.username = admin auth.user.1.password = 123456 2、配置禁止匿名登录(安全认证) 找到emqx.conf编辑## Allow anonymous authe…

NETCORE -MinIO的基本使用

NETCORE -MinIO的基本使用环境:.net6 + minio minio服务部署:https://www.cnblogs.com/1285026182YUAN/p/18308075一. 创建 net6项目 二. 安装minio nuget包 三.在appsetting.json 配置文件中设置MinIO配置 {"Logging": {"LogLevel": {"Default&q…

gwang.top:一键官网查询

gwang.top是由小章做的一款在线工具,专注于帮助用户快速查找软件、机构或公司的官方网站,本文介绍这款工具的使用。原文地址:https://itxiaozhang.com/one-click-official-site-query/ 本文配合视频食用效果最佳,视频教程在文章末尾。简介 gwang.top 是一个小章做的在线查询…

(新)app逆向二(adb操作)

一、逆向的基本流程 # 1.获取app的目标(官网,豌豆荚,下载历史老版本);尽量不要去华为,小米应用市场下载;——》拿到app放在电脑上,并且安装到手机上 # 2.使用抓包工具,手机上操作app,进行抓包是(charles,fiddler); # 3.使用反编译工具(JADX,JD_GUI),把apk问阿金反…

Docker 部署 minio

Docker 部署 miniominio是分布式文件存储一. minio的yaml部署 http版的services:minio-http:command:- server /data --console-address :9001container_name: minio-httpenvironment:- MINIO_ACCESS_KEY=username- MINIO_SECRET_KEY=qI7-****O6image: minio/minio:latestloggi…