嗯好的今天我们来谈谈cosplay

splay是一种操作,是一种调整二叉排序树的操作,但是它并不会时时刻刻保持一个平衡,因为它会根据每一次操作把需要操作的点旋转到根节点上

所谓二叉排序树,就是满足对树中的任意一个节点,它左子树上的任意一个值比它的值小,右子树上的任意一个值比它的值大的一棵二叉树 ;至于平衡:是一棵空树或任意节点的左右两个子树的深度差的绝对值不超过1(from:百度百科) 看图:

不平衡:                                                                                平衡:

   

可以观察到这两棵树都是满足⑥<⑤<④<③<①<②的大小关系,只是改变每个点的相对位置不同

然后呢,splay的真正含义就出来了:通过很多很多次变换把要处理的点放到根节点上。与此同时,我们把这种变换叫做旋转。旋转的精髓就是在不破坏树中的大小顺序的同时改变节点的位置。旋转是splay的核心(虽然大部分排序树的核心操作都是旋转),大概思路和treap是差不多的(没学过treap然后爆发出狂笑),但对于splay相对来说懒一点的是它转的时候是自动判断左旋还是右旋。每一次旋转,我们把被旋转的这个点向上转一层(也就是转到它父节点的位置上)

比如说要旋转⑤节点:                                             可以看看旋转之后的样子:

  

因为⑤<④<③,所以要把⑤旋转后应该是③的左子树(也就是④的位置),但是⑧始终是大于④的所以⑧的位置是不变的 (④的右子树),然后因为⑤>④>⑦,所以④和⑦都应该是⑤的右子树而且⑦应该是④的左子树(因为是一棵二叉树),然后可以发现与⑥,⑧没有任何关系,真正改变的就只有图中的红线(表示各个节点间的父子关系)。这就是旋转的步骤。

但如果⑤是④的右子树呢?其实是差不多的,就是左右翻转一下。再想想这个图,自己推一遍:

好的,那么旋转的普遍规律就可以看出来了:整个旋转的操作中改变的只是被旋转点、它的父节点、以及它的某一棵子树,到底是哪一棵子树是根据被旋转点与它父亲的关系来决定的:如果这个点是它父节点的左子树,那么应该调整这个点的右子树;如果是右子树,则相反。

(有两个很棒的动图来表示旋转)

       

接下来打出代码。在那之前,我们先定义数组:

son[i][0]是指点i的左节点编号,son[i][1]右节点编号

root表示当前根节点的编号

sz是指整个树的值的种数,同时用于新节点插入时的编号(可以类比时间戳)

siz[i]表示以i为顶点的树的值的个数,要计算重复出现的值(包括i节点自己)

key[i]表示节点i的值是多少

fa[i]表示节点i的父节点编号

cnt[i]表示节点i的值出现了多少次数量cnt,siz[i]和种类sz是两个东西不要搞混了)

因为旋转的方式是由它是左子树还是右子树决定的,所以我们可以先写一个函数来判断:

int get(int x){
return son[fa[x]][]==x;//如果它父节点的右儿子编号等于它那么返回1(右节点),否则返回0(是左节点)
}

接下来就是旋转了:

void rotate(int x){
int f=fa[x],ff=fa[f],w=get(x);//父节点、祖父节点、是父节点的左子树还是右子树
son[f][w]=son[x][w^];//x节点的另一个子树放给原本x节点的位置
fa[son[f][w]]=f;//更新x节点的另一个子树的父节点
son[x][w^]=f;//将父节点接到x节点的另一个子树上
fa[f]=x;
fa[x]=ff;//f、x位置互换后更新祖父节点
if(ff){//父节点不是根节点(根节点的父节点为0)
son[ff][son[ff][]==f]=x;
}
update(f);
update(x);
}

注意这里的^位运算,意思是两位不同时返回1,相同时返回0,这里^1可以快速找出另一个儿子(0^1=1,1^1=0)

这里的update是指旋转之后由于位置的改变而引起的种类总数的变化,因为每一个节点值的出现的次数是没有改变的,其实很简单就是左子树种类加上右子树种类:

void update(int x){
if(x!=){//如果是根节点,旋转时种类数始终不变
siz[x]=cnt[x];//自身值的出现次数
if(son[x][])//如果有左子树
siz[x]+=siz[son[x][]];
if(son[x][])//如果有右子树
siz[x]+=siz[son[x][]];
}
}

这只是一次操作,我们前面说了我们是把要处理的点旋转到根节点上,那么怎么做呢?循环就行了。但是还有一种特殊情况,由于这种情况都满足被旋转点和父节点都是左节点或者是右儿子,我们姑且称它为三点一线,先看图:

比如说我们这里要splay④,如果直接把④一直旋转到根节点的话就会是这样:

可以看见③还是①的左节点,相当于只是改变了④和①的关系,专业一点就是说形成了单旋使平衡树失衡。而解决的方法就是在出现三点一线时先旋转它的父节点避免单旋,正确的应该是这样:

void splay(int x){
for(int f;f=fa[x];rotate(x)){//注意旋转的始终是x
if(fa[f]){//可能存在三点一线
rotate(get(x)==get(f)?f:x);//三点一线情况判断
}
}
}

接下来是splay的插入:由于在旋转的时候,我们是建立在这棵树是有序的前提下的,而要保证这个前提,就需要从这棵树的建立开始就让它有序,所以,我们在插入的时候就必须按照排序树的规定来插入,也就是说每次插入都是从根节点开始比较大小,直到找到这个值或者是找到树的底部(这个值没有出现过):

void insect(int v){
if(sz==){//如果这棵树是空树,插入点应该为根节点
sz++;
son[][]=son[][]=fa[]=;
siz[]=cnt[]=;
root=;
key[]=v;
return;
}
int now=root,f=;//now表示现在查找到节点编号,f表示当前节点的父节点
while(){
if(key[now]==v){//如果这个值已经在树中,那么它出现的次数增加
cnt[now]++;
update(now);//更新相关点
update(f);
splay(now);//将插入的点旋转到根节点,便于下一次可能的操作
break;
}
f=now;
now=son[now][v>key[now]];//依照节点值查找位置,如果大于当前值v>key[now]=1,则在右子树范围内,反之亦然
if(now==){//树中无这个值,查找到树的底端,新建一个子节点
sz++;//新节点编号
son[sz][]=son[sz][]=;//新节点初始化
fa[sz]=f;
siz[sz]=cnt[sz]=;
son[f][v>key[now]]=sz;//判断新节点是左节点还是右节点并更新父节点
key[sz]=v;
update(f);//更新数量
splay(sz);//旋转到根节点
break;
}
}
}

接下来是查找一个值在树中排从小到大第几(是比它小的有多少个)(如果为了方便理解也可以说是从大到小求倒数第几),原理很简单,因为排序树的性质,我们可以知道只要是在这个节点的左边的都是比它小的,那么我们就可以利用之前记录好的siz来快速求出:(注意是数量,要加上重复的,比如说1,2,2,3,这个数列中,3排第4)

int find(int v){
int ans=,now=root;//ans记录已经有多少比它小的点,now表示正在寻找的节点的编号
while(){
if(v<key[now]){//如果当前节点的值大于v,那么当前节点的左子树不完全小于v,继续向当前节点的左子树寻找
now=son[now][];
}
else{//当前节点的左子树上的值必然全部小于v
ans+=(son[now][]!=?siz[son[now][]]:);//如果有左子树则直接加上左子树的数量
if(v==key[now]){//如果当前节点的值等于v,则右子树上不可能有比它小的数,所有比它小的数已经找完
splay(now);//下一次可能的操作
return ans+;//有1个数比它小那么它应该是第2,以此类推,要+1
}
ans+=cnt[now];//key[now]<v的情况,除了它的左儿子还要加上它自身的数量
now=son[now][];//右子树中可能存在比v小的值,所以在右子树中继续寻找
}
}
}

有了查找排名,如果需要知道第几是多少,也是可以求的:

int findx(int x){
int now=root;//当前节点
while(){
if(son[now][]!=&&siz[son[now][]]>=x){//如果左子树的数量大于x,就是说第x个是在左子树上(前提是有左子树)
now=son[now][];//在左子树上接着搜索
}
else{//第x个在以当前节点为顶点的树中
int less=(son[now][]!=?siz[son[now][]]:)+cnt[now];//左子树的数量(可能没有)+当前节点的值的数量
if(x<=less)//由于之前判断过是否在左子树上,并且在之后的运算中排除了所有左子树,x却不在右子树上,那么只可能是当前点的值
return key[now];
x-=less;//在右子树中还有多少值比它小,排除左子树
now=son[now][];//继续搜索
}
}
}

接下来是前驱,就是指比它小的第一个点(就是比它自己的值小而且它的前驱与它自己的值的差的绝对值最小)。在树中表现为它的左子树中最右边的那个点,这样才满足比它自身的值小并且最接近它自身的值

int query_pre(int x){
   splay(x);//首先旋转到根节点方便查找
int now=son[root][];//定位左子树
while(son[now][]!=)now=son[now][];//循环查找左子树中最右边的点
return now;//最底部跳出循环,找到答案
}

有前驱当然也有后继,类似的,后继是指比它大的第一个点(比它自己的值大而且它的后继与它自己的值的差的绝对值最小)。在树中表现为它右子树最左边的那个点:(原理一模一样)

int query_next(int x){
splay(x);
int now=son[root][];
while(son[now][]!=)now=son[now][];
return now;
}

Last but not the least:删除。这里的删除是删除一次值,就是说受cnt的影响,不一定是删一个节点。这个挺复杂的,比如说删除一个值v,首先我们用之前的find(v)将值等于v的点旋转到根上,之后要分5种情况:

  • 这个值出现了不止一次:直接减少次数,更新数量即可;
  • 整棵树只剩下它一个值 孤苦伶仃 :将树清空;
  • 这个点没有左子树:直接将右子树的顶点提出来,取代它的父节点;
  • 这个点没有右子树:直接将左子树的顶点提出来,取代它的父节点;
  • 这个点左右子树都有:先把前驱为作新树的根(叫做新根)(后继也完全没有问题,只是之后的处理要想一想是左子树还是右子树),将它旋转到根,这个时候在将原根的右儿子接到新根的左儿子上,更新即可。

这里先给出清空:(就是把所有关于这个点的东西归零

void clear(int x){
son[x][]=son[x][]=fa[x]=siz[x]=key[MX]=cnt[x]=;
}

接下来就可以开始删除了:

void del(int v){
find(v);
if(cnt[root]>){//第一种情况
cnt[root]--;
update(root);
return;
}
if(son[root][]==&&son[root][]==){//第二种情况
clear(root);
root=;//将树清空
return;
}
if(son[root][]==){//第三种情况
int old=root;
root=son[root][];
fa[root]=;//新根的父节点更新
clear(old);
return;
}
if(son[root][]==){//第四种情况
int old=root;
root=son[root][];
fa[root]=;
clear(old);
return;
}
int newroot=query_per(root),oldroot=root;
splay(newroot);//将新根转上来
fa[son[oldroot][]]=newroot;
son[root][]=son[oldroot][];//继承右子树
clear(oldroot);//旧根归零
update(root);//更新新根
}

学习笔记:平衡树-splay的更多相关文章

  1. 学习笔记--(平衡树)splay

    坑爹的splay,毁我青春,耗我钱财,颓我精力 是一种用于保存有序集合的简单高效的数据结构.伸展树实质上是一个二叉查找树.允许查找,插入,删除,删除最小,删除最大,分割,合并等许多操作,这些操作的时间 ...

  2. [学习笔记]平衡树(Splay)——旋转的灵魂舞蹈家

    1.简介 首先要知道什么是二叉查找树. 这是一棵二叉树,每个节点最多有一个左儿子,一个右儿子. 它能支持查找功能. 具体来说,每个儿子有一个权值,保证一个节点的左儿子权值小于这个节点,右儿子权值大于这 ...

  3. 【学习笔记】splay入门(更新中)

    声明:本博客所有随笔都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 前言 终于学习了 spaly \(splay\) !听说了很久,因为dalao总 ...

  4. 普通平衡树学习笔记之Splay算法

    前言 今天不容易有一天的自由学习时间,当然要用来"学习".在此记录一下今天学到的最基础的平衡树. 定义 平衡树是二叉搜索树和堆合并构成的数据结构,它是一 棵空树或它的左右两个子树的 ...

  5. [学习笔记] 平衡树——Treap

    前置技能:平衡树前传:BST 终于学到我们喜闻乐见的平衡树啦! 所以我们这次讲的是平衡树中比较好写的\(Treap\). (以后会写splay的先埋个坑在这) 好了,进入正题. step 1 我们知道 ...

  6. 学习笔记:Splay

    代码适中.非常灵活的平衡树. 需要前置:二叉搜索树. 一些基础的函数: int idx, ch[N][2], cnt[N], sz[N], fa[N]; /* idx 是节点计数, ch[i][0 / ...

  7. 学习笔记 | treap | splay

    目录 前言 treap 它的基本操作 前言 不会数据结构选手深深地感受到了来自treap的恶意QwQ 在听的时候感觉自己听得听懂的??大概只是听懂了它的意思 代码是怎么写都感觉写不好╮(╯﹏╰)╭ 菜 ...

  8. 平衡树学习笔记(3)-------Splay

    Splay 上一篇:平衡树学习笔记(2)-------Treap Splay是一个实用而且灵活性很强的平衡树 效率上也比较客观,但是一定要一次性写对 debug可能不是那么容易 Splay作为平衡树, ...

  9. BST,Splay平衡树学习笔记

    BST,Splay平衡树学习笔记 1.二叉查找树BST BST是一种二叉树形结构,其特点就在于:每一个非叶子结点的值都大于他的左子树中的任意一个值,并都小于他的右子树中的任意一个值. 2.BST的用处 ...

  10. 平衡树splay学习笔记#2

    讲一下另外的所有操作(指的是普通平衡树中的其他操作) 前一篇的学习笔记连接:[传送门],结尾会带上完整的代码. 操作1,pushup操作 之前学习过线段树,都知道子节点的信息需要更新到父亲节点上. 因 ...

随机推荐

  1. HTML+CSS教程(一)简介及其基本标签的使用方法

    一.前端 HTML(结构):HyPer TEXT Markup LanguageCSS(样式): 样式就是对于结构的一种美化JavaScript(js: 行为/ 提供了用户和界面的交互方式)jQuer ...

  2. kubernetes的Statefulset介绍

    StatefulSet是一种给Pod提供唯一标志的控制器,他可以保证部署和扩展的顺序. Pod一致性 包含次序(启动和停止次序).网络一致性.此一致性和Pod相关.与被调度到哪个Node节点无关. 稳 ...

  3. 计算某天的下一天:黑盒测试之等价类划分+JUnit参数化测试

    题目要求 测试以下程序:该程序有三个输入变量month.day.year(month.day和year均为整数值,并且满足:1≤month≤12.1≤day≤31和1900≤year≤2050),分别 ...

  4. Linux 文件常用权限

    -rw------- (600) 只有所有者才有读和写的权限 -rw-r--r-- (644) 只有所有者才有读和写的权限,组群和其他人只有读的权限 -rwx------ (700) 只有所有者才有读 ...

  5. C#多线程(16):手把手教你撸一个工作流

    目录 前言 节点 Then Parallel Schedule Delay 试用一下 顺序节点 并行任务 编写工作流 接口构建器 工作流构建器 依赖注入 实现工作流解析 前言 前面学习了很多多线程和任 ...

  6. MAC攻击及缺陷

    MAC攻击及缺陷 MAC有好几种实现方式 对MAC的攻击 重放攻击 重放攻击的防护 密钥推测攻击 MAC算法的缺陷 第三方证明 防止否认 前面我们在讲HMAC的时候简单讲过了什么是MAC消息认证码. ...

  7. Mybatis自动生成插件对数据库类型为text的处理

    2019独角兽企业重金招聘Python工程师标准>>> 如果数据库中的字段为text或者blob这种大文本类型,在使用MybatisGenerator工具自动生成代码的时候会将其进行 ...

  8. ffmpeg+SDL2实现的音频播放器V2.0(无杂音)

    1. 前言 目前为止,学习了并记录了ffmpeg+SDL2显示视频以及事件(event)的内容. 这篇中记录ffmpeg+SDL2播放音频,没加入事件处理. 接下来加入事件处理并继续学习音视频同步,再 ...

  9. 自动安装带nginx_upstream_check_module模块的Nginx脚本

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/bin/bash    useradd -s /sbin/no ...

  10. Codeforces Round 623(Div. 2,based on VK Cup 2019-2020 - Elimination Round,Engine)D. Recommendations

    VK news recommendation system daily selects interesting publications of one of n disjoint categories ...