史上最详尽平衡树(splay树)讲解

平衡树【Splay树】学习小记 平衡树,顾名思义,平衡的搜索二叉树。常见的平衡树都能将树的深度保持在lg⁡n\lg_nlgn​的级别内,防止退化成链。一些平衡树可以通过旋转、分裂、合并等操作完成更加高级的、二叉搜索树做不到的操作。 阅读详情

首先声明:万分感谢gty大哥的帮助!这年头能找到简单易懂的数组版平衡树模板只能靠学长了!


变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于权值),size[i]表示包括i的这个子树的大小;sz为整棵树的大小,root为整棵树的根。

再介绍几个基本操作:

【clear操作】:将当前点的各项值都清0(用于删除之后)

  1. inline void clear(int x){  
  2.      ch[x][0]=ch[x][1]=f[x]=cnt[x]=key[x]=size[x]=0;  
  3. }  

【get操作】:判断当前点是它父结点的左儿子还是右儿子
  1. inline int get(int x){  
  2.      return ch[f[x]][1]==x;  
  3. }  
【update操作】:更新当前点的size值(用于发生修改之后)
  1. inline void update(int x){  
  2.      if (x){  
  3.           size[x]=cnt[x];  
  4.           if (ch[x][0]) size[x]+=size[ch[x][0]];  
  5.           if (ch[x][1]) size[x]+=size[ch[x][1]];  
  6.      }  
  7. }  
下面boss来了:

【rotate操作图文详解】


这是原来的树,假设我们现在要将D结点rotate到它的父亲的位置。

step 1:

找出D的父亲结点(B)以及父亲的父亲(A)并记录。判断D是B的左结点还是右结点。

step 2:

我们知道要将Drotate到B的位置,二叉树的大小关系不变的话,B就要成为D的右结点了没错吧?

咦?可是D已经有右结点了,这样不就冲突了吗?怎么解决这个冲突呢?

我们知道,D原来是B的左结点,那么rotate过后B就一定没有左结点了对吧,那么正好,我们把G接到B的左结点去,并且这样大小关系依然是不变的,就完美的解决了这个冲突。


这样我们就完成了一次rotate,如果是右儿子的话同理。step 2的具体操作:

我们已经判断了D是B的左儿子还是右儿子,设这个关系为K;将D与K关系相反的儿子的父亲记为B与K关系相同的儿子(这里即为D的右儿子的父亲记为B的左儿子);将D与K关系相反的儿子的父亲即为B(这里即为把G的父亲记为B);将B的父亲即为D;将D与K关系相反的儿子记为B(这里即为把D的右儿子记为B);将D的父亲记为A。

最后要判断,如果A存在(即rotate到的位置不是根的话),要把A的儿子即为D。

显而易见,rotate之后所有牵涉到变化的父子关系都要改变。以上的树需要改变四对父子关系,BG DG BD AB,需要三个操作(BG BD AB)。

step 3:update一下当前点和各个父结点的各个值

【代码】

  1. inline void rotate(int x){  
  2.      int old=f[x],oldf=f[old],which=get(x);  
  3.      ch[old][which]=ch[x][which^1];f[ch[old][which]]=old;  
  4.      f[old]=x;ch[x][which^1]=old;  
  5.      f[x]=oldf;  
  6.      if (oldf)  
  7.           ch[oldf][ch[oldf][1]==old]=x;  
  8.      update(old);update(x);  
  9. }  

【splay操作】

其实splay只是rotate的发展。伸展操作只是在不停的rotate,一直到达到目标状态。如果有一个确定的目标状态,也可以传两个参。此代码直接splay到根。

splay的过程中需要分类讨论,如果是三点一线的话(x,x的父亲,x的祖父)需要先rotate x的父亲,否则需要先rotate x本身(否则会形成单旋使平衡树失衡)

  1. inline void splay(int x){  
  2.      for (int fa;(fa=f[x]);rotate(x))  
  3.           if (f[fa])  
  4.                rotate((get(x)==get(fa)?fa:x));  
  5.      root=x;  
  6. }  

【insert操作】

其实插入操作是比较简单的,和普通的二叉查找树基本一样。

step 1:如果root=0,即树为空的话,做一些特殊的处理,直接返回即可。

step 2:按照二叉查找树的方法一直向下找,其中:

如果遇到一个结点的关键字等于当前要插入的点的话,我们就等于把这个结点加了一个权值。因为在二叉搜索树中是不可能出现两个相同的点的。并且要将当前点和它父亲结点的各项值更新一下。做一下splay。

如果已经到了最底下了,那么就可以直接插入。整个树的大小要+1,新结点的左儿子右儿子(虽然是空)父亲还有各项值要一一对应。并且最后要做一下他父亲的update(做他自己的没有必要)。做一下splay。

  1. inline void insert(int v){  
  2.      if (root==0) {sz++;ch[sz][0]=ch[sz][1]=f[sz]=0;key[sz]=v;cnt[sz]=1;size[sz]=1;root=sz;return;}  
  3.      int now=root,fa=0;  
  4.      while (1){  
  5.           if (key[now]==v){  
  6.                cnt[now]++;update(now);update(fa);splay(now);break;  
  7.           }  
  8.           fa=now;  
  9.           now=ch[now][key[now]<v];  
  10.           if (now==0){  
  11.                sz++;  
  12.                ch[sz][0]=ch[sz][1]=0;key[sz]=v;size[sz]=1;  
  13.                cnt[sz]=1;f[sz]=fa;ch[fa][key[fa]<v]=sz;  
  14.                update(fa);  
  15.                splay(sz);  
  16.                break;  
  17.           }  
  18.      }  
  19. }  

【find操作】查询x的排名

初始化:ans=0,当前点=root

和其它二叉搜索树的操作基本一样。但是区别是:

如果x比当前结点小,即应该向左子树寻找,ans不用改变(设想一下,走到整棵树的最左端最底端排名不就是1吗)。

如果x比当前结点大,即应该向右子树寻找,ans需要加上左子树的大小以及根的大小(这里的大小指的是权值)。

不要忘记了再splay一下

  1. inline int find(int v){  
  2.      int ans=0,now=root;  
  3.      while (1){  
  4.           if (v<key[now])  
  5.                now=ch[now][0];  
  6.           else{  
  7.                ans+=(ch[now][0]?size[ch[now][0]]:0);  
  8.                if (v==key[now]) {splay(now);return ans+1;}  
  9.                ans+=cnt[now];  
  10.                now=ch[now][1];  
  11.           }  
  12.      }  
  13. }  
【findx操作】找到排名为x的点

初始化:当前点=root

和上面的思路基本相同:

如果当前点有左子树,并且x比左子树的大小小的话,即向左子树寻找;

否则,向右子树寻找:先判断是否有右子树,然后记录右子树的大小以及当前点的大小(都为权值),用于判断是否需要继续向右子树寻找。

  1. inline int findx(int x){  
  2.      int now=root;  
  3.      while (1){  
  4.           if (ch[now][0]&&x<=size[ch[now][0]])  
  5.                now=ch[now][0];  
  6.           else{  
  7.                int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];  
  8.                if (x<=temp)  
  9.                     return key[now];  
  10.                x-=temp;now=ch[now][1];  
  11.           }  
  12.      }  
  13. }  

【求x的前驱(后继),前驱(后继)定义为小于(大于)x,且最大(最小)的数】

这类问题可以转化为将x插入,求出树上的前驱(后继),再将x删除的问题。

其中insert操作上文已经提到。

【pre/next操作】

这个操作十分的简单,只需要理解一点:在我们做insert操作之后做了一遍splay。这就意味着我们把x已经splay到根了。求x的前驱其实就是求x的左子树的最右边的一个结点,后继是求x的右子树的左边一个结点(想一想为什么?)

  1. inline int pre(){  
  2.      int now=ch[root][0];  
  3.      while (ch[now][1]) now=ch[now][1];  
  4.      return now;  
  5. }  
  6.   
  7. inline int next(){  
  8.      int now=ch[root][1];  
  9.      while (ch[now][0]) now=ch[now][0];  
  10.      return now;  
  11. }  

【del操作】

删除操作是最后一个稍微有点麻烦的操作。

step 1:随便find一下x。目的是:将x旋转到根。

step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。

step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。

step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子)

剩下的就是它有两个儿子的情况。

step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。

  1. inline void del(int x){  
  2.      int whatever=find(x);  
  3.      if (cnt[root]>1) {cnt[root]--;return;}  
  4.      //Only One Point  
  5.      if (!ch[root][0]&&!ch[root][1]) {clear(root);root=0;return;}  
  6.      //Only One Child  
  7.      if (!ch[root][0]){  
  8.           int oldroot=root;root=ch[root][1];f[root]=0;clear(oldroot);return;  
  9.      }  
  10.      else if (!ch[root][1]){  
  11.           int oldroot=root;root=ch[root][0];f[root]=0;clear(oldroot);return;  
  12.      }  
  13.      //Two Children  
  14.      int leftbig=pre(),oldroot=root;  
  15.      splay(leftbig);  
  16.      f[ch[oldroot][1]]=root;  
  17.      ch[root][1]=ch[oldroot][1];  
  18.      clear(oldroot);  
  19.      update(root);  
  20.      return;  
  21. }  


【总结】

平衡树的本质其实是二叉搜索树,所以很多操作是基于二叉搜索树的操作。

splay的本质是rotate,旋转其实只是为了保证二叉搜索树的平衡性。

所有的操作一定都满足二叉搜索树的性质,所有改变父子关系的操作一定要update。

关键是理解rotate,splay的原理以及每一个操作的原理。

所有的操作均来自bzoj3224 普通平衡树  附链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3224

完整代码:http://blog.youkuaiyun.com/clove_unique/article/details/50636361

【笔记】Splay 详细解读 Splay 是一种平衡树,并且是一棵二叉搜索树(BST)。它满足对于任意节点,都有左子树上任意点的值 < 当前节点的值 < 右子树上任意点的值。优点:支持多种操作。缺点:常数较大。单次操作均摊复杂度Olog⁡nO(\log n)Ologn。(注:关于 Splay 单次操作均摊复杂度的证明见OI-WikiSplay 基于旋转操作维护树的平衡。旋转分为左旋 (zag) 和右旋 (zig)。旋转,即在保证平衡树中序遍历不变的前提下,改变整个树的深度。 阅读详情

相关推荐

平衡树·splay

文章目录1.About splay2.基本操作2.1 数组是干啥的?2.2 基本操作 1.About splay splaysplaysplay是实现平衡树的一种方法 他需要满足一个条件,就是对于一个点为根节点的子树,他的左子树全部小于根节点,右子树全部大于根节点 那他为什么叫这个名字呢? 我们查询一下百度翻译 但是事实上,感觉splaysplaysplay跟张开没什么关系… splayspla...

NephrenRuqInsania 3260

平衡树详解(Splay)

引入 先看例题:(洛谷 P3369 【模板】普通平衡树) 您需要写一种数据结构,来维护一些数,其中需要提供以下操作: 1.插入 xxx 数 2.删除 xxx 数(若有多个相同的数,因只删除一个) 3.查询 xxx 数的排名(排名定义为比当前数小的数的个数 +1+1+1 ) 4.查询排名为 xxx 的数 5.求 xxx 的前驱(前驱定义为小于 xxx,且最大的数) 6.求 xxx 的后继(后继定义为大于 xxx,且最小的数) 显然可以使用BST(二叉搜索树)完成,它的时间复杂度是 O(logn)O(log

Wu_while的博客 1万+

简析平衡树(三)——浅谈Splay

前言 原本以为TreapTreapTreap已经很难了,学习了SplaySplaySplay,我才知道,没有最难,只有更难。(强烈建议先去学一学TreapTreapTreap再来看这篇博客) 简介 SplaySplaySplay是平衡树中的一种,除了平衡树所共有的作用之外,它还可以维护区间翻转,这也是它能成为LCTLCTLCT辅助树的原因(不过LCTLCTLCT并不是这篇博客所探讨的内容...

CXR的博客 1428

普通平衡树 Splay

普通平衡树,Splay

wanglianda2007的博客 1809

平衡树——Splay及其维护的相关信息

无限Orz Tarjan大神。 Splay是一个神奇的数据结构,它可以维护一些数的权值集合、一个序列甚至是一棵树(一片森林)。 权值 其实权值线段树也可以处理平衡树的问题。在权值线段树外面套一个树状数组(树状数组套主席树)可以解决更复杂的带修改和区间查询权值相关的问题,那不要那个树状数组不就能解决一个平衡树的问题啦? 不过今天不讨论权值线段树,我们只讲Splay。 二叉查找树 学S...

star_city 863

平衡树SPLAY

平衡树SPLAY 平衡树这个东西一点用没有,非常有用,而且锻炼码力,是个非常好的模板题! 那么SPLAY这个东西 十分的毒瘤 我只调了一上午就调出来了!(我真棒) 首先 我们要知道平衡树是一棵二叉查找树 他可以处理:加点,删点,前驱,后继,num的排名,某排名的num,合并两棵平衡树,分离两棵平衡树 关于其原理 网上有很多 而且比较简单 对于splay来说 原理并不是最难的 码代码才是最难的! ...

qq_45342177的博客 324

(模板)Splay 平衡树

不会讲解,直接上板子,按照洛谷P3369的要求 #include&lt;iostream&gt; #include&lt;cstring&gt; #include&lt;cstdio&gt; using namespace std; const int MAXN=1000000; int ch[MAXN][2],f[MAXN],size[MAXN],cnt[MAXN],key[MAXN]; ...

HumveeA6的博客 334

大语言模型的三种主要架构 Decoder-Only、Encoder-Only、Encoder-Decoder

f基于 Transformer 模型以非灰色显示:decoder-only 模型在蓝色分支,encoder-only 模型在粉色分支,encoder-decoder 模型在绿色分支。模型在时间线上的垂直位置表示它们的发布日期。开源模型由实心方块表示,而闭源模型由空心方块表示。右下角的堆积条形图显示了各公司和机构的模型数量。从时间轴上,我们可以看到:2021年前,当 OpenAI 决定在,他们实际上是在逆流而上。

u010899985的博客 2万+

史上最详尽的平衡树(splay)讲解与模板

首先声明:万分感谢gty大哥的帮助!这年头能找到简单易懂的数组版平衡树模板只能靠学长了!   变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于权值),size[i]表示包括i的这个子树的大小;sz为整棵树的大小,root为整棵树的根。 再介绍...

zyf2000 3万+

微信小程序网关逆向分析:从协议、签名到会话管理的安全架构剖析

在现代Web与移动应用开发中,API网关作为客户端与后端服务之间的关键通信枢纽,承担着协议转换、流量调度、安全防护与性能优化等核心职责。其工作原理通常涉及统一的请求路由、身份认证、参数校验与响应聚合。从技术价值看,深入理解网关机制不仅能帮助开发者构建更健壮、可扩展的微服务架构,还能为安全评估、性能调优与故障排查提供基础。在移动生态如微信小程序中,网关设计尤为关键,它需要处理微信特有的登录态、数据加密与防重放攻击等安全需求。本文聚焦于微信小程序网关的逆向分析方法,通过结合网络抓包与代码反编译技术,深入解析其通

weixin_29323493的博客 442

Carsim2019与Simulink (Matlab2018b)联合仿真成功(超详细)

Carsim与Simulink联合仿真成功

强强的博客 3万+

史上第二详尽的平衡树(Splay)详解

谢鸣:本文来自zyf2000学姐的blog,原题为“史上最详尽的平衡树(splay)讲解与模板”,我在这里拿过来使用,命名为“史上第二详尽的平衡树(Splay)详解”,并加上了一些新的操作. 变量声明:f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于权值)...

A_Comme_Amour的博客 1万+

如何用IDEA查看jar包中代码

我们在使用Java进行开放时经常会需要下载些第三方的jar包进行相应的开发。有时候相关jar包的使用文档不足,满足不了我们的特定开发使用要求,这就需要我们查看jar包内的相关代码逻辑,本文将基于IDEA工具介绍如何查看相关jar包的代码逻辑

mystonelxj的博客 2280

splay平衡树

splay平衡树

ha_ing的博客 1707

ISO2626 --- Item Definition相关项定义

本文并不是针对ISO26262的一个项目,而是针对ISO26262的规范,我们在相关项定义阶段,我们需要做什么,我们的文档需要包含哪些内容的一个描述。

wj博客 909

Android Telephony主要模块及功能

Telephony的代码主要由五个模块组成 1.Telephony Frameworks 代码区域: frameworks/opt/telephony/ 其中主要的文件在如下目录 /frameworks/opt/telephony/src/java/com/android/internal/telephony/ 其中比较常见的有以下几类文件 1.BaseCommands、RIL构成R

泊客 7721

【数据结构】Splay树 + 文艺平衡树

就是一个长这样的树,树中每个结点都有一个父结点(除了根结点没有父结点)和最多两个子结点,每个结点的左儿子一定比它小,右儿子一定比它大。这棵树的先序遍历很容易知道就是:1 2 3 4 5 6 7 (根左右)我们还可以从另一个角度理解先序遍历:把整棵树映射到 x 轴上,也就是把它压扁也就是这样:先序遍历从左到右读出来就可以了。

Texcavator的博客 1510

在WIN10系统中安装TIA博途V18,重启后提示安装介质不可用,请插入DVD 或检查网络连接的解决办法

在WIN10系统中安装TIA博途V18,重启后提示安装介质不可用,请插入DVD 或检查网络连接的解决办法

Robot_PLC_HMI_Automation 1万+

史上最详尽的LCT讲解

终于填完了这个天坑!

薇小薇 7724

uniapp - 详细实现移动端公众号 H5 网页授权登录流程及示例代码,申请测试公众号全流程及本地调试无需部署上线服务器就能直接调起微信登陆授权页面,手机移动端h5公众号页面利用微登录拿到用户数据!

uni-app,微信公众号,h5网页,uniapp网页端,手机端H5公众号页面,微信网页登陆,uniApp详细实现公众号的登录,微信授权第三方网站登录,流程及逻辑详细教程,OAuth2.0,公众号网页授权,微信网页授权通过code换取网页授权,redirect_url,参数错误,解决,报错,无法调起,如何申请测试公众号,微信公众平台测试号的申请与使用,获取用户openid手机号昵称头像,利用测试号实现本地调试微信登录授权,本地无法调试怎么办,测试公众号,在开发环境下怎么测试,用项目本地ip地址,微信公众号平

🏆 前端领域优质创作者 5063

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

平衡树算法总结&专题训练2(有旋平衡树:AVL 树,Splay)

平衡树算法总结&专题训练2(有旋平衡树:AVL 树,Splay)1.概述2.有旋平衡树-AVL 树 1.概述 在 平衡树算法总结&专题训练1 中我们着重讲解了两种无旋平衡树:替罪羊树,FHQ Treap。 那么在这篇博文中我们将讲解 AVL 树和 Splay 这两种有旋平衡树。 有旋平衡树维持树平衡的方法通常都是树旋转。 我专门写了一篇文章来讲树旋转->link,请务必保证在学透树旋转之后再往下看,否则会看不懂的。 那么还是以模板为例,看看 AVL 树和 Splay 又会有怎样的表现。

Plozia 的小窝 683

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

SPLAY树

前言 根据研究表明,90%的访问都是针对10%的元素。提高这90%的访问的效率,就明显改善了对整个访问的效率。 核心思想:把最近访问过的结点提升到根,后续访问的深度将减小。把这种提升结点的操作称为“伸展Splay”。以伸展操作为基础的二叉排序树也就相应称为伸展树(Splay Tree)。 开始 基于前言中十分有用(坑爹 )的话,于是我们需要用SPLAY树来优化时间,即把刚操作的点引到根下,即S...

TL的博客 1007

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

详解Splay平衡树

也就是该树的中序遍历是有序的, 可以实现查询第。:不管对Splay进行什么操作, 将当前操作的点。, 需要取值, 并且计算最终薪资的时候需要加。暴力做法是并查集 + 排序, 算法时间复杂度。能处理的问题(因为可以加入延迟标记)这样操作已经证明了, 均摊时间复杂度。Splay的特殊操作, 可以支持。类似于线段树, 树形结构需要通过。, 旋转之后也是这样的大小关系。如果没了解过线段树,大数, 查询一个数字的。节点, 将节点转到树根。, 可以记录一个偏移量。可以使用splay实现。的代表元素, 假设是。

Ayanami_Rei的博客 951

史上最菜的splay讲解

先了解一下什么是平衡树吧。 之后进行各种玄学操作,但都离不开平衡树的基本性质: 满足根存在一性质,大于左子树的同一性质,小于右子树的同一性质 请记住这句话。 定义结构体(以普通平衡树为例): struct node{int d,n,c,f,son[2];}t[N];int len,root;//son[0]为左,son[1]为右 先讲讲五个基本操作吧。 updateupdateupdate操作(...

lb2003 244

acm-【平衡树】学习笔记(Splay,Treap,fhq Treap,替罪羊树,红黑树,avl tree,B树,B+树)

引言 本文的写作目的主要是为了作者日后复习,也供浏览本文的群众以参考,若有不严谨之处欢迎在评论区指出。 下面 目录引言Splay Splay Splay又名伸展树,是一种比较常见的平衡树,它的核心操作主要是 ...

qq_30361651的博客 1320
上一篇: RMQ( 区间最值,区间频繁次数)
下一篇: MySQL5.7MSI安装详解
luckcircle
博客等级 码龄10年 13粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值