Splay是众多平衡树之一,它的功能十分强大,但常数极大。在LCT和许多数据结构中都能用到。

Splay的核心操作,就是rotate。为了使树不是一条链,而是平衡的,我们需要旋转来维护形态。理论很简单,下面来看一下如何实现。

图片转自洛谷

我们注意到,旋转之后,这棵树依然保持着正常的大小关系。

来尝试着写代码。

观察到,y的c儿子依旧是y的右儿子,而x,y的父子关系发生了改变,x的左儿子不变,x的右儿子变成了y的左儿子。

这是右旋。

明显的,我们发现不只有这一种情况。单旋的Splay,会被出题人的构造数据卡成链。我们先来处理双旋操作,再来处理链。

Rotate:

  1. inline void rotate(node *x){
  2. node *y=x->fa,*z=y->fa;int k=y->ch[1]==x;
  3. z->ch[z->ch[1]==y]=x;x->fa=z;
  4. y->ch[k]=x->ch[k^1];x->ch[k^1]->fa=y;
  5. x->ch[k^1]=y;y->fa=x;pushup(y);pushup(x);
  6. }

注意,结构体里面存了x节点的父亲,左右儿子,值,子树大小以及此处值相同的节点个数。

其中,k=1表示y的右儿子是x,这是逻辑表达式。

理解了rotatw操作,开始讲一下它的核心:Splay操作。

还记得我说的单旋吗?在一些情况下,如果你只旋转当前节点,会依旧成为一条链。

我们如何避免这件事呢?

我们观察到,当x,x的fa以及grandfa在一条线上时,转x是无意义的。

所以,我们要rotate(y).

就这么简单。

这样一直到x转到目标点。

代码:

  1. inline void splay(node *x,node *goal){
  2. node *y,*z;
  3. while(x->fa!=goal){
  4. y=x->fa,z=y->fa;
  5. if(z!=goal)(z->ch[1]==y)^(y->ch[1]==x)?rotate(x):rotate(y);
  6. rotate(x);
  7. }if(goal==null)rt=x;
  8. }

注意,此处我用的是指针,所以不是!goal,而是goal==null.

下面,就Splay模板开始讲解。

首先,插入操作。

我们插入一个点,首先判断,如果它是一棵空树,那就把树的根节点新建出来(指针),值为插入的值,return即可。

否则,往下沉。

有两种可能:在树中找的过程中,如果有值一样的,就把x旋转到根,并且把x的num++.

否则,新建节点,记录最后找到的点。

最后找到的点是新建点的father.

插入的值是新建点的值。

插入完成。

代码有些冗长,注意理解背诵。

  1. inline node *new_(){
  2. node *x=new node;
  3. x->ch[0]=x->ch[1]=x->fa=null;x->siz=x->num=1;return x;
  4. }
  5. inline void insert(int v){
  6. node *x=rt;
  7. if(x==null){rt=new_();rt->v=v;return;}
  8. node *fa=null;
  9. while(x!=null){
  10. fa=x;if(v!=x->v)x=x->ch[v>=x->v];
  11. else {splay(x,null);x->siz++;x->num++;return;}
  12. }x=new_();fa->ch[v>=fa->v]=x;
  13. x->fa=fa;x->v=v;
  14. splay(x,null);
  15. }

下一步操作,删除(del,Del)

首先,我们知道,删除的值一定在树中。

于是,我们从根开始搜索。

找到之后,对于这个点,进行del.

先把这个点(x)splay到根,并且把它的子树大小(siz)和同值节点数量(num)--.

这时,有两种结果。

首先,于这个点相同值的点还有。

这样,直接return即可。

否则,如果这棵树只有这一个点,那就把根赋值为null,然后return即可。

否则,顺着x的左右孩子向下找。

找到最后的节点,把它旋转到x的孩子上。

这需要更新x了,也就是删除。

x的孩子认y(转上来的节点)做爹,y是根,爹为null,delete(x)即可。

删除完成。

代码:

  1. inline void del(node *x){
  2. splay(x,null);x->siz--;x->num--;int k;
  3. if(x->num)return;
  4. if(x->ch[0]!=null)k=0;
  5. else if(x->ch[1]!=null)k=1;
  6. else {rt=null;delete(x);return;}
  7. node *y=x->ch[k];
  8. while(y->ch[k^1]!=null)y=y->ch[k^1];
  9. splay(y,x);y->ch[k^1]=x->ch[k^1];
  10. x->ch[k^1]->fa=y;y->fa=null;rt=y;
  11. delete(x);
  12. }inline void Del(int v){
  13. node *x=rt;
  14. while(x!=null){
  15. if(v!=x->v)x=x->ch[v>=x->v];
  16. else {del(x);return;}
  17. }
  18. }

然后,就是最熟悉的求排名,求排名为k的数,以及前驱,后继。

rank:首先找v是否存在。不存在输出0.

如果存在,那么它的排名就是当前点的左孩子的子树大小+1.

完成。

求排名为k的数:

定义c为当前点的左子树大小。

如果当前点的左子树大小超过了k,说明它不是要找的点,向它的左子树找。

如果c<k了,说明找到了。

k就把c和当前点的num减掉。

如果k<0了,输出当前x的值即可。

否则,找x的右子树。

完成。

一起说前驱后继吧。

前驱,小于v的最大数。

后继,大于v的最小数。

从根开始搜索,定义一个极值用来更新。

这个不好阐述,看代码理解吧。

上代码:

  1. inline int rank(int v){
  2. node *x=rt;
  3. while(x!=null){
  4. if(v>x->v)x=x->ch[1];
  5. else if(v<x->v)x=x->ch[0];
  6. else{splay(x,null);return x->ch[0]->siz+1;}
  7. }return 0;
  8. }inline int kth(int k){
  9. node *x=rt;int c;
  10. while(x!=null){
  11. c=x->ch[0]->siz;
  12. if(c>=k)x=x->ch[0];
  13. else{
  14. k-=c+x->num;
  15. if(k<=0){splay(x,null);return x->v;}
  16. x=x->ch[1];
  17. }
  18. }return 0;
  19. }
  20. inline int pre(int v){
  21. node *x=rt;int ans=-inf;
  22. while(x!=null){
  23. if(v>x->v)ans=max(ans,x->v),x=x->ch[1];
  24. else x=x->ch[0];
  25. }return ans;
  26. }
  27. inline int last(int v){
  28. node *x=rt;int ans=inf;
  29. while(x!=null){
  30. if(v<x->v)ans=min(ans,x->v),x=x->ch[0];
  31. else x=x->ch[1];
  32. }return ans;
  33. }

至此,Splay告一段落。

Splay被称为序列之王,它还可以实现许多功能。

下次讲树套树的时候,一并说一下。

注:模板来自dalao——wjr.

Splay浅谈的更多相关文章

  1. 浅谈splay(点的操作)

    浅谈splay(点的操作) 一.基本概念 splay本质:二叉查找树 特点:结点x的左子树权值都小于x的权值,右子树权值都大于x的权值 维护信息: 整棵树:root 当前根节点  sz书上所有结点编号 ...

  2. Lct浅谈

    Lct浅谈 1.对lct的认识 ​ 首先要知道$lct$是什么.$lct$的全称为$link-cut-tree$.通过全称可以看出,这个数据结构是维护树上的问题,并且是可以支持连边断边操作.$lct$ ...

  3. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  4. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  5. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  6. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

  7. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  8. 浅谈angular2+ionic2

    浅谈angular2+ionic2   前言: 不要用angular的语法去写angular2,有人说二者就像Java和JavaScript的区别.   1. 项目所用:angular2+ionic2 ...

  9. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

随机推荐

  1. Photon Server伺服务器在LoadBalancing的基础上扩展登陆服务

    一,如何创建一个Photon Server服务 参见此博客 快速了解和使用Photon Server 二, 让LoadBalancing与自己的服务一起启动 原Photonserver.config文 ...

  2. 【web系统UI自动化】关于UI自动化的总结

    实施过了web系统的UI自动化,回顾梳理下,想到什么写什么,随时补充. 首先,自动化测试不是手动测试的替代品,是比较好的补充,而且不是占大比重的补充. 70%的测试工作集中在底层接口测试和单元测试,2 ...

  3. 03 父子组件sync&update

    父组件传给子组件是基本数据类型. 父组件 <template> <el-container class="consele-container"> <e ...

  4. 杭电oj2093题,Java版

    杭电2093题,Java版 虽然不难但很麻烦. import java.util.ArrayList; import java.util.Collections; import java.util.L ...

  5. 20190930-02 Redis持久化方式一:RDB及修改RDB的默认持久化策略 000 032

  6. Life is not the amount of breath you take.

    It's the moments that take you breath away.

  7. MySQL查询更新所有满足条件的数据

    -- 将订单表所有的状态改成1update oc_repair_preorder a inner join (select id,`status` from oc_repair_preorder) b ...

  8. whlie do-whlie

    switch语句  用于根据多个不同条件执行不同动作.   while 循环         while循环基本语法:    条件初始化;   while(条件表达式){     //条件表达式就是判 ...

  9. 尚硅谷阳哥JVM笔记

    JVM体系结构 类加载器(快递员): 只负责加载java文件,编译后的class文件在文件开头有特定的文件表示,将class文件字节码内容从硬盘加载到JVM内存中并将这些内容转换成方法区的运行时数据结 ...

  10. java虚拟机5 字节码

    java字节码本质是java程序的格式化表示,便于机器处理.所以他是java程序的另一种表示,java程序包含的信息他都包含并且更加结构化. java虚拟机字节码格式: magic 魔数,标识该文件是 ...