浅谈splay(点的操作)
浅谈splay(点的操作)
一、基本概念
splay本质:二叉查找树
特点:结点x的左子树权值都小于x的权值,右子树权值都大于x的权值
维护信息:
整棵树:root 当前根节点 sz书上所有结点编号
结点:f[] 父节点编号 ch[][2] 孩子结点编号,0左1右
siz[] 以结点为根的子树大小(包括自己) cnt[]自己出现的次数
key[] 结点权值
二、基本操作
插入insert、删除del、查询x的排名findpos、查询排名为x的数findx、查找前驱pre、查找后继nex
核心操作:伸展操作splay
part 1:这么多操作难免会更改节点信息,我们先思考如何维护这些信息
siz,cnt 可以这样维护
void update(int x)
{
siz[x]=cnt[x];
if(ch[x][]) siz[x]+=siz[ch[x][]];
if(ch[x][]) siz[x]+=siz[ch[x][]];
}
f,ch,root 要在splay操作中修改
splay操作:就是讲结点x不断旋转至根节点
旋转过程谁成为谁的左右孩子,自己根据大小关系判断总结即可
旋转代码:
int getson(int x)
{
return ch[f[x]][]==x;
}
void rotate(int x)
{
int fa=f[x],fafa=f[fa],k=getson(x);
ch[fa][k]=ch[x][k^];f[ch[fa][k]]=fa;//对应蓝色线,调整x另一方向的孩子和x父节点的关系
ch[x][k^]=fa;f[fa]=x;//对应红色线 ,调整x和父节点的关系
f[x]=fafa;
if(fafa) ch[fafa][ch[fafa][]==fa]=x;//对应紫色线 ,调整x和父节点的父节点的关系
update(fa);update(x);
}
小细节:为什么先update(fa),再update(x) ,因为旋转前,fa是x的父节点,经旋转后,fa变为x的孩子节点,update操作是根据左右孩子子树大小更新的
调用:(双旋) 个人对于双旋的一点理解:http://www.cnblogs.com/TheRoadToTheGold/p/6372344.html
void splay(int x)
{
for(int fa;fa=f[x];rotate(x))
if(f[fa]) rotate(getson(x)==getson(fa) ? fa :x);
root=x;
}
小细节:fa=f[x],1、每执行一次旋转,更新一次da 2、fa==true时才进行
part 2:
A、插入数x insert(int x)
分为3种情况
1、树为空,直接插入x,并让x成为根节点
2、树不为空 ①树中已有x,x出现次数+1,以x为根的子树大小+1,旋转x至根节点
②树中没有x,在适当位置插入x,旋转x至根节点
void create(int x)
{
sz++;key[sz]=x;
cnt[sz]=siz[sz]=;
ch[sz][]=ch[sz][]=f[sz]=;
}
void insert(int x)//插入结点x
{
if(!root) create(x),root=sz;//splay为空
else
{
int now=root,fa=;
while()
{
if(key[now]==x)//树中有x
{
cnt[now]++;
siz[now]++;
splay(now);
break;
}
fa=now;
now=ch[fa][x>key[fa]];
if(!now)
{
create(x);
f[sz]=fa;
ch[fa][x>key[fa]]=sz;
splay(sz);
break;
}
}
}
}
小细节:为什么要splay?仅仅是插入不是插进去就行吗?成不成为根节点有什么关系?
这是为了查找比x小/大的第一个数做铺垫,因为有可能x在树中没有出现过,所以先插入x,再找前驱/后继,这就可以直接从根节点找起,不用再找一次x的位置,最后删除x
(下面的E、F)
B、查询排名为x的数(从小到大)
记得平衡树怎么查找第k小吗?——如果左子树大小<k,找左孩子,否则找右孩子。
类比一下可以得出
1、如果x<当前点左子树大小,找左孩子,这里注意一个小细节是要先判断当前点是否有左孩子
2、否则 定义变量temp=当前结点出现次数+结点左子树大小
①、如果x<=temp 那么这个结点就是答案
因为既然x>=当前点左子树大小,那么他要么是当前点,要么在当前点的右子树
又因为x<=当前点+左子树大小+当前点出现次数,那么他是当前点
②、如果x>temp 那么x减去temp,找右孩子
int findx(int x)
{
int now=root;
while()
{
if(ch[now][]&&x<=siz[ch[now][]]) now=ch[now][];//千万不要漏了ch[now][0]==true
else
{
int temp=(ch[now][] ? siz[ch[now][]] : )+cnt[now];
if(x<=temp) return key[now];
x-=temp;
now=ch[now][];
}
}
}
C、查询x的排名
想想splay本质是二叉查找树,不难得出
1、如果x<当前节点权值 查找左孩子
2、否则 ,先令ans加上当前节点左子树大小
①、如果x=当前节点权值,旋转当前节点至根节点,返回ans+1
因为此时ans不包括当前节点,所以要+1
②、如果x>当前节点权值,ans加上当前节点出现次数,查找右孩子
int findpos(int x)
{
int now=root,ans=;
while()
{
if(x<key[now]) now=ch[now][];
else
{
ans+=ch[now][] ? siz[ch[now][]] : ;
if(x==key[now])
{
splay(now);
return ans+;
}
ans+=cnt[now];
now=ch[now][];
}
}
}
小细节:为什么要splay?
为了下面的删除操作做铺垫,删除数x需要先找到x的位置,删除操作是在x是根节点的基础上进行的(下面的F)
D、查找比x小的第一个数
这就有2种可能:x在树中,x不在树中
x在树中就是查找x的前驱,那么不在树中呢?
我们可以向在树中插入x,在查找前驱,最后再删除x
如何查找前驱? 转向x的左孩子l,然后在l的子树里一直往右找
调用代码:
insert(x);printf("%d\n",key[pre()]);del(x);break;
查找前驱代码:
int pre()
{
int now=ch[root][];
while(ch[now][]) now=ch[now][];
return now;
}
E、查询比x大的第一个数
同理D
直接给代码
insert(x);printf("%d\n",key[nex()]);del(x);break;
int nex()
{
int now=ch[root][];
while(ch[now][]) now=ch[now][];
return now;
}
F、删除数x
分为5种情况
首先,你要先找到x在哪儿,将其旋转至根节点,这里可以直接调用findpos函数
然后,分类讨论(此时根节点就是数x,所以此后操作变为删除根节点)
删除:结点所有信息清0即可
void clear(int x)
{
ch[x][]=ch[x][]=cnt[x]=siz[x]=f[x]=key[x]=;
}
1、根节点在splay树中出现次数>1 根节点的出现次数-1,子树大小-1
if(cnt[root]>)
{
cnt[root]--;siz[root]--;
return;
}
2、否则 ① 根节点既没有左孩子又没有右孩子,说明树中只有这一个结点,直接删去,并 root=0
if(!ch[root][]&&!ch[root][])
{
clear(root);
root=;//千万不要漏了这一句
return;
}
② 根节点没有左孩子,说明树左边为空,那么只需把根节点的右孩子提为根节点,删除原根节点 小细节:新根节点的父节点置为0
if(!ch[root][])
{
int tmp=root;
root=ch[root][];
f[root]=;//不要漏了它
clear(tmp);
return;
}
③ 根节点没有右孩子,与②同理
if(!ch[root][])
{
int tmp=root;
root=ch[root][];
f[root]=;
clear(tmp);
return;
}
④ 根节点既有左孩子又有右孩子
我们可以先把x的前驱l旋转为根节点
手动模拟一下过程可以发现:
在l成为根节点的前一步,一定是x的左孩子 ,这说明了l成为根节点后,x不会有左孩子
那么我们就可以直接把x的右孩子提到x的位置,删除x即可
int pre1=pre(),tmp=root;//tmp现在相当于x的位置
splay(pre1);//x前驱旋转为根节点 ,经过此操作后,根节点变为x的前驱
ch[root][]=ch[tmp][];//x的右孩子提到x的位置
f[ch[tmp][]]=root;//更新父节点
clear(tmp);//删除x
update(root);
为什么要在x是根节点的基础上执行删除操作?
因为splay要维护cnt、siz等信息
如果x不是根节点,x删除,x以上所有结点关于个数之类的信息都要更改
而如果x是根节点,x删除,不会影响其他结点
splay 插入insert、删除del、查询x的排名findpos、查询排名为x的数findx、查找前驱pre(第一个比x小的数)、查找后继nex(第一个比x大的数)完整代码
题目描述 您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作: .插入x数 .删除x数(若有多个相同的数,因只删除一个) .查询x数的排名(若有多个相同的数,因输出最小的排名) .查询排名为x的数 .求x的前驱(前驱定义为小于x,且最大的数) .求x的后继(后继定义为大于x,且最小的数) 输入输出格式 输入格式:
第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号(<=opt<=) 输出格式:
对于操作3,,,6每行输出一个数,表示对应答案
代码背景简述
题目来源:https://www.luogu.org/problem/show?pid=3369
#include<cstdio>
#define N 1000000
using namespace std;
int f[N],ch[N][],key[N],cnt[N],siz[N],sz,root;
void update(int x)
{
siz[x]=cnt[x];
if(ch[x][]) siz[x]+=siz[ch[x][]];
if(ch[x][]) siz[x]+=siz[ch[x][]];
} int pre()
{
int now=ch[root][];
while(ch[now][]) now=ch[now][];
return now;
}
int nex()
{
int now=ch[root][];
while(ch[now][]) now=ch[now][];
return now;
}
int getson(int x)
{
return ch[f[x]][]==x;
}
void rotate(int x)
{
int fa=f[x],fafa=f[fa],k=getson(x);
ch[fa][k]=ch[x][k^];f[ch[fa][k]]=fa;
ch[x][k^]=fa;f[fa]=x;
f[x]=fafa;
if(fafa) ch[fafa][ch[fafa][]==fa]=x;
update(fa);update(x);
}
void splay(int x)
{
for(int fa;fa=f[x];rotate(x))
if(f[fa]) rotate(getson(x)==getson(fa) ? fa :x);
root=x;
}
int findpos(int x)
{
int now=root,ans=;
while()
{
if(x<key[now]) now=ch[now][];
else
{
ans+=ch[now][] ? siz[ch[now][]] : ;
if(x==key[now])
{
splay(now);
return ans+;
}
ans+=cnt[now];
now=ch[now][];
}
}
}
int findx(int x)
{
int now=root;
while()
{
if(ch[now][]&&x<=siz[ch[now][]]) now=ch[now][];//千万不要漏了ch[now][0]==true
else
{
int temp=(ch[now][] ? siz[ch[now][]] : )+cnt[now];
if(x<=temp) return key[now];
x-=temp;
now=ch[now][];
}
}
}
void clear(int x)
{
ch[x][]=ch[x][]=cnt[x]=siz[x]=f[x]=key[x]=;
}
void create(int x)
{
sz++;key[sz]=x;
cnt[sz]=siz[sz]=;
ch[sz][]=ch[sz][]=f[sz]=;
}
void insert(int x)
{
if(!root) create(x),root=sz;
else
{
int now=root,fa=;
while()
{
if(key[now]==x)
{
cnt[now]++;
siz[now]++;
splay(now);
break;
}
fa=now;
now=ch[fa][x>key[fa]];
if(!now)
{
create(x);
f[sz]=fa;
ch[fa][x>key[fa]]=sz;
splay(sz);
break;
}
}
}
} void del(int x)
{
int t=findpos(x);
if(cnt[root]>)
{
cnt[root]--;siz[root]--;
return;
}
if(!ch[root][]&&!ch[root][])
{
clear(root);
root=;
return;
}
if(!ch[root][])
{
int tmp=root;
root=ch[root][];
f[root]=;
clear(tmp);
return;
}
if(!ch[root][])
{
int tmp=root;
root=ch[root][];
f[root]=;
clear(tmp);
return;
}
int pre1=pre(),tmp=root;
splay(pre1);
ch[root][]=ch[tmp][];
f[ch[tmp][]]=root;
clear(tmp);
update(root);
}
int main()
{
int n,opt,x;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d%d",&opt,&x);
switch(opt)
{
case :insert(x);break; //插入x
case :del(x);break;//删除x
case :printf("%d\n",findpos(x));break;//查询x的排名
case :printf("%d\n",findx(x));break;//查询排名为x的数
case :insert(x);printf("%d\n",key[pre()]);del(x);break;//查找第一个小于x的数
case :insert(x);printf("%d\n",key[nex()]);del(x);break;//查找第一个大于x的数
}
}
}
浅谈splay(点的操作)的更多相关文章
- 浅谈.net中数据库操作事务
.net中的事务 关键几点 概念:1:什么是事务 2:什么时候用事务 3:基本的语法 (1): 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit).事务通常 ...
- 浅谈splay
\(BST\) 二叉查找树,首先它是一颗二叉树,其次它里面每个点都满足以该点左儿子为根的子树里结点的值都小于自己的值,以该点右儿子为根的子树里结点的值都大于自己的值.如果不进行修改,每次查询都是\(O ...
- 浅谈splay的双旋
昨晚终于明白了splay双旋中的一些细节,今日整理如下 注:题目用的2002HNOI营业额统计,测试结果均来及codevs 网站的评测结果 http://codevs.cn/problem/1296/ ...
- 简析平衡树(三)——浅谈Splay
前言 原本以为\(Treap\)已经很难了,学习了\(Splay\),我才知道,没有最难,只有更难.(强烈建议先去学一学\(Treap\)再来看这篇博客) 简介 \(Splay\)是平衡树中的一种,除 ...
- 浅谈Notepad++选中行操作+快捷键+使用技巧【超详解】
Notepad++选中行操作 快捷键 使用技巧 用Notepad++写代码,要是有一些重复的代码想copy一下,还真不容易,又得动用鼠标,巨烦人.... 有木有简单的方法呢,确实还是有的不过也不算太好 ...
- 浅谈如何用Java操作MongoDB
NoSQL数据库因其可扩展性使其变得越来越流行,利用NoSQL数据库可以给你带来更多的好处,MongoDB是一个用C++编写的可度可扩展性的开源NoSQL数据库.本文主要讲述如何使用Java操作Mon ...
- 浅谈MySQL多表操作
字段操作 create table tf1( id int primary key auto_increment, x int, y int ); # 修改 alter table tf1 modif ...
- 浅谈“Mysql”的基础操作语句
/*-------------------------------------------读者可以补充内容到下面-------------------------------------------- ...
- 浅谈配置chrome浏览器允许跨域操作的方法
浅谈配置chrome浏览器允许跨域操作的方法 一:(Lying人生感悟.可忽略) 最近有一天,对着镜子,发现满脸疲惫.脸色蜡黄.头发蓬松.眼神空洞,于是痛诉着说生活的不如意,工作没激情,工资不高,一个 ...
随机推荐
- Beta 冲刺1
队名:日不落战队 安琪(队长) 过去两天完成了那些任务 修改个人信息界面. 修改手写涂鸦界面. 接下来的任务 改进手写涂鸦,加入其他功能. 还剩下的任务 社交模块功能. 遇到的困难 无. 有哪些收获和 ...
- 【Coursera】基于朴素贝叶斯的中文多分类器
一.算法说明 为了便于计算类条件概率\(P(x|c)\),朴素贝叶斯算法作了一个关键的假设:对已知类别,假设所有属性相互独立. 当使用训练完的特征向量对新样本进行测试时,由于概率是多个很小的相乘所得, ...
- 【CSAPP笔记】8. 汇编语言——数据存储
下面介绍一些C语言中常见的特殊的数据存储方式,以及它们在汇编语言中是如何表示的. 数组 数组是一种将标量数据聚集成更大数据类型的方式.实现数组的方式其实十分简单,也非常容易翻译成机器代码.C语言的一个 ...
- 微服务注册与发现 —— eureka
基础概念 在微服务系统中,服务的注册和发现是第一步,常用的有: Eureka:https://github.com/Netflix/eureka Zookeeper:https://zookeeper ...
- Beta 冲刺 (3/7)
队名:日不落战队 安琪(队长) 过去两天完成了那些任务 上传个人信息. 接下来的任务 建立和上传收藏夹. 还剩下的任务 完善手写涂鸦. 社交模块. 遇到的困难 暂无. 有哪些收获和疑问 收获:okht ...
- ejb与javabean的区别总结
EJB的英文全称是企业级的JavaBean 两者是完全不同的 JavaBean是一个组件,而EJB就是一个组建框架 JavaBean面向的是业务逻辑和表示层的显示,通过编写一个JavaBean,可以将 ...
- js数组遍历 千万不要使用for...in...
昨天做个下拉框 扩充了一下数组的方法 Array.prototype.remove = function (val) { var index = this.indexOf(val); if (inde ...
- 服务 在初始化安装时发生异常:System.IO.FileNotFoundException: 未能加载文件或******
这个问题是在用installutil.exe安装服务时候碰到的 解决方法就是把installutil.exe文件直接复制到要安装的目录下 installutil.exe的所在位置 windows/mi ...
- mac下面安装redis
本文只记录了在homebrew下面安装redis的过程,过程比较简单,作为自己以后翻阅用吧, 首先安装homebrew,打开终端,在终端下面允许下面的命令(不要用root用户): /usr/bin/r ...
- [转帖]七牛云对HTTPS 的解释
感觉对RTT 还有 建立连接的说明挺好的 转帖一下 学习 https://www.cnblogs.com/qiniu/p/6856012.html 序•魔戒再现 几天前,OpenSSL ...