最新要闻
- 当前速读:女子厨房接水时速热水龙头突然爆炸冒白烟:爆炸声堪比雷响
- 【天天时快讯】特斯拉Model 3追尾公交1死1伤 事故已影响销售:网友关心刹车问题
- 全球快资讯丨女子屋内湿度表1年数值不变 好奇拆下检查后无语:还以为是坏的
- 曾经很火但消失了的APP!网友第一个想到的是”腾讯微博“
- 环球即时:女子入住网红酒店发现床垫有尿渍:满房一股味
- 防AI越界!微软将出手:把必应聊天回复限制在5条以内
- 天天热文:全球最高安全标准 我国自研华龙一号技术:太平岭核电预计2025年投产发电
- 天天热资讯!挑战全网最土的“公主下午茶” 让人看饿:网友感慨羞辱多少爱装腔作势人
- 每日消息!称霸意甲的非洲新一代神锋,奥斯梅恩正在征服足坛
- 让NV对30系显卡降价不可能!厂商清仓RTX 3080:2年后价格重回首发价
- 全球观速讯丨《文明6》已玩腻 等了7年的《文明7》官宣:主管大换血
- 【世界独家】全天候显示能掉多少电?iOS 16.4告诉你
- 环球观速讯丨公司回应要求员工扫厕所:这是福利 每月有几十元奖励
- 焦点日报:冲击40亿有望!《流浪地球2》累计票房已超38亿
- 女子情人节翻垃圾桶捡到金链:最后被前主人要回 网友热议
- 观天下!假的!马斯克否认修改算法推荐自己帐号 将追责说谎员工
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
头条焦点:伸展树(Splay)详解
引入
在一条链中,二叉查找树的时间复杂度就会退化成 \(O(n)\),这时我们就需要平衡树来解决这个问题。
\(Splay\)(伸展树)是平衡树的一种,它的每一步插入、查找和删除的平摊时间都是 \(O(log_2 n)\),出于对编程复杂度和算法性能的考虑,这是 OI 中常用的算法。
(资料图片仅供参考)
性质
\(Splay\) 本质上还是对二叉查找树的优化。所以它也具备二叉查找树的性质,即左子树任意节点的值 \(<\) 根节点的值 \(<\) 右子树任意节点的值。
操作
数组含义
root | tot | fa[i] | ch[i][0] | ch[i][1] | val[i] | size[i] | cht[i] |
---|---|---|---|---|---|---|---|
根节点编号 | 节点数量 | 父节点编号 | 左儿子编号 | 右儿子编号 | 节点权值 | 子树大小 | 节点权值出现次数 |
基本操作
maintain(x)
:维护子树大小
void Splay::maintain(int x){size[x] = size[ch[x][0]] + size[ch[x][1]] + cnt[x];return ;}
get(x)
:查询该节点是其父亲节点的左子树还是右子树
bool Splay::get(int x){if( x == ch[fa[x]][1] )return 1;return 0;}
clear(x)
:清理该节点
void Splay::clear(int x){ch[x][0] = ch[x][1] = fa[x] = val[x] = size[x] = cnt[x] = 0;return ;}
旋转
旋转操作实际上是让某一个节点上移一个位置。
旋转操作需要保证,二叉查找树的性质不会改变,节点维护的信息依然正确,\(root\) 必须指向旋转后的根节点。
若节点 x 是其父亲的左节点
由于 \(x\) 的右儿子的权值大于 \(x\) 的权值,且 \(x\) 及其子树都属于 \(y\) 的左子树(即 \(x\) 的右子树实际上小于 \(y\) 的权值),所以我们将 \(x\) 的右子树改为 \(y\) 的左子树。
- 将 \(x\) 的右儿子变成 \(y\) 的左儿子,如果 \(x\) 有右儿子的话就让它的父亲变成 \(y\)。
ch[y][0] = ch[x][1]; fa[ch[x][1]] = y;
由于 \(y\) 及其子树的权值都大于 \(x\) 的权值,所以我们让 \(y\) 成为 \(x\) 的右儿子。
使 \(y\) 成为 \(x\) 的右儿子,\(x\) 变为 \(y\) 的父亲。
ch[x][chk^1] = y; fa[y] = x;
如果 \(x\) 此时不是根节点,那么 \(x\) 将继承原先 \(y\) 作为 \(z\) 的儿子的位置(\(x\) 取代 \(y\) 成为 \(z\) 的左儿子或右儿子)。
fa[x] = z; if(z) ch[z][y == ch[z][1]] = x;
由此我们得到了节点 \(x\) 上升一个位置的树,显然,这棵树仍然满足二叉搜索树的性质。
实现
void Splay::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);// 别忘了维护子树大小 return ;}
代码中采用异或来实现左右不同旋转情况,当然我们可以写两个函数分别来实现左旋和右旋。
伸展
伸展操作是在保持伸展树性质的前提下,将节点 \(x\) 转移到根节点。在这个转移过程中,我们分为三种情况。
首先我们设节点 \(x\) 的父节点为节点 \(y\),若节点 \(y\) 有父节点,其父节点为 \(z\)。
第一种情况:\(y\) 是根节点
- 若 \(x\) 是 \(y\) 的左儿子,我们进行一次右旋操作
- 若 \(x\) 是 \(y\) 的右儿子,我们进行一次左旋操作
第二种情况:\(y\) 不是根节点,且 \(x\) 和 \(y\) 同为左儿子或右儿子
- 若 \(x\) 和 \(y\) 同时是各自父节点的左儿子,则进行两次右旋操作
- 若 \(x\) 和 \(y\) 同时是各自父节点的右儿子,则进行两次左旋操作
第三种情况:\(y\) 不是根节点,且 \(x\) 和 \(y\) 一个为左儿子一个为右儿子
- 若 \(x\) 是 \(y\) 的左儿子,\(y\) 是 \(z\) 的右儿子,则进行一次右旋 - 左旋操作
- 若 \(x\) 是 \(y\) 的右儿子,\(y\) 是 \(z\) 的左儿子,则进行一次左旋 - 右旋操作
实现
void Splay::splay(int x){for(int i = fa[x];i = fa[x],i; rotate(x))if( fa[i] ){if( get(x) == get(i) )rotate(i);elserotate(x);}root = x;return ;}
插入
如果树为空,则直接插入根节点
如果找到了一个节点权值与插入权值相等,则增大该节点并维护信息,再进行 Splay 操作
否则接着往下找,要是找到空节点就直接插入
实现
void Splay::insert(int v){if( root == 0 ){tot ++;val[tot] = v;cnt[tot] ++;root = tot;maintain(root);return ;}int cur = root,x = 0;while(1){if( val[cur] == v ){cnt[cur] ++;maintain(cur);maintain(x);splay(cur);break;}x = cur;cur = ch[cur][val[cur] < v];if( cur == 0 ){tot ++;val[tot] = v;cnt[tot] ++;fa[tot] = x;ch[x][val[x] < v] = tot;maintain(tot);maintain(x);splay(tot);break;}}}
寻找数 \(x\) 的排名(比它小的数的个数值 + 1)
若 \(x\) 小于当前节点权值,则向左子树查找
若 \(x\) 大于当前节点权值,则答案加上左子树大小
size[i]
和当前节点权值出现次数cnt[i]
若找到与 \(x\) 相等的节点,则返回当前答案 \(+ 1\)
实现
int Splay::find_rank(int v){int ans = 0,cur = root;while(1){if( v < val[cur] )cur = ch[cur][0];else{ans += size[ch[cur][0]];if( v == val[cur] ){splay(cur);return ans + 1;}ans += cnt[cur];cur = ch[cur][1];}}}
寻找排名为 \(x\) 的数的值
\(v\) 表示剩余排名,在初始排名的条件下不断减少。
若左子树不为空且剩余排名 \(v\) 小于等于左子树大小 \(size\)(即 \(x\) 在左子树),向左子树查找
否则减去左子树大小和根的出现次数作为剩余排名 \(v\)。若 \(v\leq 0\),则返回根节点,否则向右子树查找。
实现
int Splay::find_num(int v){int cur = root;while(1){if( ch[cur][0] != 0 && v <= size[ch[cur][0]] )cur = ch[cur][0];else{v -= cnt[cur] + size[ch[cur][0]];//.//if( v <= 0 ){splay(cur);return val[cur];}cur = ch[cur][1];}}}
查询前驱(小于 \(x\) 的最大的数)
先插入节点 \(x\),这样 \(x\) 就处在了根节点的位置。
此时 \(x\) 的左子树都小于 \(x\),寻找 \(x\) 的左子树的最右边节点即小于 \(x\) 的最大的数。
实现
int Splay::pre(){int cur = ch[root][0];if( cur == 0 )return cur;while( ch[cur][1] )cur = ch[cur][1];splay(cur);return cur;}
查询后继(大于 \(x\) 的最小的数)
基本思想与查询前驱相同。
先插入节点 \(x\),这样 \(x\) 就处在了根节点的位置。
此时 \(x\) 的右子树都大于 \(x\),寻找 \(x\) 的右子树的最左边节点即大于 \(x\) 的最小的数。
实现
int Splay::next(){int cur = ch[root][1];if( cur == 0 )return cur;while( ch[cur][0] )cur = ch[cur][0];splay(cur);return cur;}
合并
对于合并两棵树,其中一棵树的值都小于另一棵树的值。
我们可以找到较小一棵树的最大值 \(x\),将其旋转到根节点。
再把较大一棵树作为 \(x\) 的右子树插入。
删除
首先将 \(x\) 转移到根节点
若 \(x\) 值不只一个,即 \(cnt[x] > 1\),则直接减一退出即可。
否则将它的左右两棵子树合并
实现
void Splay::del(int v){find_rank(v);/////if( cnt[root] > 1 ){cnt[root] --;maintain(root);return ;}if( ch[root][0] == 0 && ch[root][1] == 0 ){clear(root);root = 0;return ;}if( ch[root][0] == 0 ){int cur = root;root = ch[root][1];fa[root] = 0;clear(cur);return ;}if( ch[root][1] == 0 ){int cur = root;root = ch[root][0];fa[root] = 0;clear(cur);return ;}int cur = root;int x = pre();fa[ch[cur][1]] = x;ch[x][1] = ch[cur][1];clear(cur);maintain(root);return ;}
模板题
Luogu P3369 【模板】普通平衡树
完整代码
#includeusing namespace std;const int MAXN = 114514; int n;int root;// 根节点 int ch[MAXN][2],fa[MAXN];// 子节点( 0 左 1 右 ) 父节点 int val[MAXN];// 权值 int size[MAXN];// 子树大小 int cnt[MAXN];// 这个权值出现的次数 int tot;// 节点个数 struct Splay{void maintain(int x);// 维护子树大小 bool get(int x);// 查找这个节点是父亲的左子树还是右子树 void clear(int x);// 销毁这个节点 void rotate(int x);// 旋转 void splay(int x);// 伸展操作 void insert(int v);// 插入数 v int find_rank(int v);// 查询数 v 的排名 int find_num(int v);// 查询排名为 v 的数 int pre();// 查询根节点的前驱 int next();// 查询根节点的后继 void del(int v);// 删除 v }tree;void Splay::maintain(int x){size[x] = size[ch[x][0]] + size[ch[x][1]] + cnt[x];return ;}bool Splay::get(int x){if( x == ch[fa[x]][1] )return 1;return 0;}void Splay::clear(int x){ch[x][0] = 0;ch[x][1] = 0;fa[x] = 0;val[x] = 0;size[x] = 0;cnt[x] = 0;return ;}void Splay::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);return ;}void Splay::splay(int x){for(int i = fa[x];i = fa[x],i; rotate(x))if( fa[i] ){if( get(x) == get(i) )rotate(i);elserotate(x);}root = x;return ;}void Splay::insert(int v){if( root == 0 ){tot ++;val[tot] = v;cnt[tot] ++;root = tot;maintain(root);return ;}int cur = root,x = 0;while(1){if( val[cur] == v ){cnt[cur] ++;maintain(cur);maintain(x);splay(cur);break;}x = cur;cur = ch[cur][val[cur] < v];if( cur == 0 ){tot ++;val[tot] = v;cnt[tot] ++;fa[tot] = x;ch[x][val[x] < v] = tot;maintain(tot);maintain(x);splay(tot);break;}}}int Splay::find_rank(int v){int ans = 0,cur = root;while(1){if( v < val[cur] )cur = ch[cur][0];else{ans += size[ch[cur][0]];if( v == val[cur] ){splay(cur);return ans + 1;}ans += cnt[cur];cur = ch[cur][1];}}}int Splay::find_num(int v){int cur = root;while(1){if( ch[cur][0] != 0 && v <= size[ch[cur][0]] )cur = ch[cur][0];else{v -= cnt[cur] + size[ch[cur][0]];//.//if( v <= 0 ){splay(cur);return val[cur];}cur = ch[cur][1];}}}int Splay::pre(){int cur = ch[root][0];if( cur == 0 )return cur;while( ch[cur][1] )cur = ch[cur][1];splay(cur);return cur;}int Splay::next(){int cur = ch[root][1];if( cur == 0 )return cur;while( ch[cur][0] )cur = ch[cur][0];splay(cur);return cur;}void Splay::del(int v){find_rank(v);/////if( cnt[root] > 1 ){cnt[root] --;maintain(root);return ;}if( ch[root][0] == 0 && ch[root][1] == 0 ){clear(root);root = 0;return ;}if( ch[root][0] == 0 ){int cur = root;root = ch[root][1];fa[root] = 0;clear(cur);return ;}if( ch[root][1] == 0 ){int cur = root;root = ch[root][0];fa[root] = 0;clear(cur);return ;}int cur = root;int x = pre();fa[ch[cur][1]] = x;ch[x][1] = ch[cur][1];clear(cur);maintain(root);return ;}int main(){scanf("%d",&n);for(int i = 1,opt,x;i <= n; i++){scanf("%d%d",&opt,&x);if( opt == 1 )tree.insert(x);else if( opt == 2 )tree.del(x);else if( opt == 3 )///printf("%d\n",tree.find_rank(x));else if( opt == 4 )////printf("%d\n",tree.find_num(x));else if( opt == 5 ){tree.insert(x);printf("%d\n",val[tree.pre()]);tree.del(x);}else{tree.insert(x);printf("%d\n",val[tree.next()]);tree.del(x);}}return 0;}
关键词: 二叉查找树
-
报道:django连接ubuntu22下的mysql8
1 安装mysql(这里就不过多赘述了)sudoapt-getinstallmysql-server2 登录mysql(1)在根目录 etc mysql debian cnf,使用默
来源: 头条焦点:伸展树(Splay)详解
当前简讯:期末复习——内存管理
报道:django连接ubuntu22下的mysql8
打造自己的ChatGPT:逐字打印的流式处理
从矩阵的谱半径到神经网络梯度消失
当前速读:女子厨房接水时速热水龙头突然爆炸冒白烟:爆炸声堪比雷响
【天天时快讯】特斯拉Model 3追尾公交1死1伤 事故已影响销售:网友关心刹车问题
全球快资讯丨女子屋内湿度表1年数值不变 好奇拆下检查后无语:还以为是坏的
世界时讯:【JS】Pug调用自定义JS函数
头条:Java正则匹配域名白名单
曾经很火但消失了的APP!网友第一个想到的是”腾讯微博“
环球即时:女子入住网红酒店发现床垫有尿渍:满房一股味
防AI越界!微软将出手:把必应聊天回复限制在5条以内
天天热文:全球最高安全标准 我国自研华龙一号技术:太平岭核电预计2025年投产发电
天天热资讯!挑战全网最土的“公主下午茶” 让人看饿:网友感慨羞辱多少爱装腔作势人
【新要闻】组合数学_第1章_排列与组合
每日消息!称霸意甲的非洲新一代神锋,奥斯梅恩正在征服足坛
让NV对30系显卡降价不可能!厂商清仓RTX 3080:2年后价格重回首发价
全球观速讯丨《文明6》已玩腻 等了7年的《文明7》官宣:主管大换血
环球关注:tui.editor一款功能强大的markdown编辑器
热消息:关于python中将字典的所有key组成一个列表的方式
【环球报资讯】Cesium CallbackProperty(十五)
【世界独家】全天候显示能掉多少电?iOS 16.4告诉你
环球观速讯丨公司回应要求员工扫厕所:这是福利 每月有几十元奖励
焦点日报:冲击40亿有望!《流浪地球2》累计票房已超38亿
女子情人节翻垃圾桶捡到金链:最后被前主人要回 网友热议
全球速读:【算法训练营day48】LeetCode198. 打家劫舍 LeetCode213. 打家劫舍II LeetCode337. 打家劫舍III
GitHub 入门 与 2023年2月18日10:29:02
观天下!假的!马斯克否认修改算法推荐自己帐号 将追责说谎员工
环球微动态丨颠覆性创新 潍柴全球首发大功率SOFC燃料电池:研发花了20亿
环球新资讯:学习笔记——尚好房项目的数据库建表文件
VirtualBox 配置虚拟机 Host-only 和 Nat
世界百事通!win系统提示请插入多卷集的最后一张磁盘解决方法
第三章 计算机进行小数运算时出错的原因
全球聚焦:北江纺织:拟冲刺上交所IPO上市,预计募资4.22亿元,近年综合毛利率逐年下降
【环球速看料】五菱会玩!城市玩乐潮品SUV悦也“小书包”是块屏:可自定义内容
天天看点:打造名族品牌!杨元庆:联想核心生产制造还是立足中国
天天快播:[数据结构] AVL树
全球头条:超越GPS主导国内导航定位 北斗日定位量超3000亿次
焦点简讯:努比亚Z50 Ultra保护壳泄漏:后置摄像模组巨大无比
全球快看:院士称我国已经具备ChatGPT算力基础 关键在如何爆发
全球新消息丨工作至死:日本789万老人还在打零工
资讯:菲律宾一飞机早上起飞后失联:近期第二起
(数据库系统概论|王珊)第四章数据库安全性:习题
快播:橙色奶油冰淇淋层蛋糕食谱
世界要闻:精装版吉利星越L?领克DX11最新谍照曝光:首上魅族车机
天天消息!秒美国资费!每月198 还能更便宜:我国千兆宽带将全面普及 第二批城市名单来了
焦点报道:java便捷的word导出工具(officejj)
当前观点:关于 The River All Red (Tr.许渊冲) 的一点感想
高尔夫美女参战《蚁人3》
网友曝光《狂飙》拍摄地有人竖牌“拍视频5元1次”:官方回应来了
天天精选!三亚去世侏儒抹香鲸被解剖:胃内有大量塑料/线虫 导致无法进食
4.打包子应用 投票
环球今日讯!极兔一面:10亿级ES海量搜索狂飙10倍,该怎么办?
一文让你彻底了解ChatGPT
焦点快看:要控制人类节奏!聊天机器人爱上用户并诱其离婚 微软出手限制了
年制绿氢3万吨、绿氧24万吨!我国全球最大绿氢项目开工
刘强东完了!章泽天官宣喜讯,被出轨4年。网友:这反击漂亮!
环球观点:数据结构刷题2023.02.18小记
环球精选!特斯拉追尾公交致1死 车主呼吁放开单踏板模式:为何老是失控?这是原因
环球资讯:1986年拍摄的泰坦尼克号残骸视频首次公开:残骸尺寸巨大
当前滚动:如何保存石榴
当前观点:iPhone 15 Pro CAD渲染图对比iPhone 14 Pro:改用USB C端口、相机更凸起、边框更窄
今日热门!渔民出海偶遇100多只海豚逐浪嬉戏:场面很美很壮观
苹果iOS 16.4首个公测版发布:普通用户将告别测试版
天天观焦点:油电平价!比亚迪秦PLUS DM-i 2023冠军版杀疯了:9.98万就能买DM-i超级混动
start+up韩剧在线观看_start up
环球快消息!女子不吃碳水半年狂减46斤 结果脱发、怕冷:治了半年
时讯:老黄厨房新菜来了 NVIDIA确认GTC 2023大会3月20日开幕
环球观天下!为什么这年头是个人就能造车?这事真就没门槛?
比半张A4纸还小 惠普1L迷你主机实测:3050 Ti独显给力
环球头条:iPhone安装不了第三方App?没关系它会出手!
世界简讯:咬牙耳朵里面疼怎么办_耳朵外轮廓疼怎么办
当前简讯:《星际2》冠军为孙哥黄哥剃光头:“卤蛋”情绪稳定
Vue急速入门-4
python项目中的“填坑”记录
自命为缓存之王的Caffeine(6)
MyBatis-Plus (SpringBoot2 版) Learning Day01
百事通!河南安阳一楼盘推出0首付0月供购房,这是什么操作?
交个朋友陷恶意裁员风波 没有了罗永浩还能走多远
世界快资讯丨口碑又崩了 漫威大片《蚁人3》豆瓣开分6.4:量子力学也带不动
【世界快播报】红旗接入百度AI文心一言 打造国产豪车品牌标杆
【当前独家】物理老师用《塞尔达传说:荒野之息》讲解小船过河原理:林克听了都说好!
世界头条:144MB缓存游戏神U!锐龙9 7950X3D跑分首曝:果然不出所料
每日热门:小米13 Ultra渲染图曝光:中分造型四摄
世界观热点:剪绳子问题 之动态规划 及 大数越界情况下的求余问题
千万小心二手RTX 20显卡!新套路出现:黑片秒变白片
日本MRJ刚失败 印度也要自研国产飞机:可载100人
女子第一次打到无人驾驶网约车:十分激动
每日热门:支持30+种中外语言!搜狗输入法力挺麒麟OS
世界微头条丨网站设计师招聘_网站设计师
世界热点!3、TreeMap源码解析
每片5.5元!绿联苹果钢化膜促销:适用iPhone 14/13/12系列
每日视讯:河南发布电动车佩戴头盔规定草案:未戴拒不改正罚20元
全球观天下!苦等3年微软终于点头:苹果M1/M2 Mac正式支持运行Win11
天天短讯!李荣浩新歌《乌梅子酱》火了!乌梅子酱淘宝搜索量暴涨200倍
新资讯:1999元!小米米家智能除湿机50L发布:100平除湿 梅雨季不怕了
当前信息:阿里一面:谈一下你对DDD的理解?2W字,帮你实现DDD自由
看热讯:关于ChatGPT,我们到底在担心什么?
【世界新要闻】在centos stream 9上搭建k8s最新版本(当前:v1.26.1)集群环境