前言

上一节中,我们讲述了Splay的核心操作rotate与splay

本节我会教大家如何用这两个函数实现各种强大的功能

为了方便讲解,我们拿这道题做例题来慢慢分析

利用splay实现各种功能

首先,我们需要定义一些东西

各种指针

    struct node
{
int v;//权值
int fa;//父亲节点
int ch[2];//0代表左儿子,1代表右儿子
int rec;//这个权值的节点出现的次数
int sum;//子节点的数量
};
int tot;//tot表示不算重复的有多少节点

rotate

splay

这两个函数就不讲了,前面已经讲的挺详细了

插入

根据前面讲的,我们在插入一个数之后,需要将其旋转到根

首先

当这棵树已经没有节点的时候,我们直接新建一个节点就好

inline int newpoint(int v,int fa)//v:权值;fa:它的爸爸是谁
{
tree[++tot].fa=fa;
tree[tot].v=v;
tree[tot].sum=tree[tot].rec=1;
return tot;
}

当这可树有节点的时候,我们根据二叉查找树的性质,不断向下走,直到找到一个可以插入的点,注意在走的时候需要更新一个每个节点的sum值

void Insert(int x)
{
int now=root;
if(root==0) {newnode(x,0);root=tot;}//
else
{
while(1)
{
T[now].sum++;
if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
int nxt=x<T[now].val?0:1;
if(!T[now].ch[nxt])
{
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root);return ;
}
now=T[now].ch[nxt];
}
}
}

删除

删除的功能是:删除权值为v的节点

我们不难想到:我们可以先找到他的位置,再把这个节点删掉

int find(int v)
{
int now=root;
while(1)
{
if(tree[now].v==v) {splay(now,root);return now;}
int nxt=v<tree[now].v?0:1;
if(!tree[now].ch[nxt])return 0;
now=tree[now].ch[nxt];
}
}

这个函数可以找到权值为v的节点的位置,比较好理解,注意别忘记把找到的节点splay到根

另外我们还需要一个彻底删除的函数

update:

这个函数其实是不需要的,因为后面的节点一定会覆盖前面的节点

inline void dele(int x)
{
tree[x].sum=tree[x].v=tree[x].rec=tree[x].fa=tree[x].ch[0]=tree[x].ch[1]=0;
if(x==tot) tot--;
}

接下来的任务就是怎么样才能保证删除节点后整棵树还满足二叉查找树的性质

注意:我们在查找完一个节点的时候已经将他旋转到根了,所以他左边一定都比他小,除此之外没有比他小的节点了(否则还要考虑他父亲比他小的情况)

那么此时会出现几种情况

  • 权值为v的节点已经出现过

    这时候直接把他的rec和sum减去1就好
  • 本节点没有左右儿子

    这样的话就成了一棵空树
  • 本节点没有左儿子

    直接把他的右儿子设置成根
  • 既有左儿子,又有右儿子

    在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子

最后把这个节点删掉就好

void delet(int x)
{
int pos=find(x);
if(!pos) return ;
if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;}
else
{
if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
else
{
int left=T[pos].ch[0];
while(T[left].ch[1]) left=T[left].ch[1];
splay(left,T[pos].ch[0]);
connect(T[pos].ch[1],left,1);
connect(left,0,1);//
update(left);
}
}
}

查询x数的排名

update in 2018.4.10

以前的自己too naive

这个函数这么写就好

int rak(int val)
{
int pos=find(val);
return T[ls(pos)].siz+1;
}

这个简单,如果我们找到了权值为x的节点,那么答案就是他的左子树的大小+1

否则的话根据二叉查找树的性质不断的向下走就可以,注意如果这次是向右走的话答案需要加上它左子树的大小和这个节点的rec值

int rank(int v)// 查询值为v的数的排名
{
int ans=0,now=root;
while(1)
{
if(tree[now].v==v) return ans+tree[tree[now].ch[0]].sum+1;
if(now==0) return 0;
if(v<tree[now].v) now=tree[now].ch[0];
else ans+=tree[tree[now].ch[0]].sum+tree[now].rec,now=tree[now].ch[1];
}
if(now) splay(now,root);
return 0;
}

查询排名为x的数

这个操作就是上面那个操作的逆向操作

用used变量记录该节点以及它的左子树有多少节点

如果x>左子树的数量且< used,那么当前节点的权值就是答案

否则根据二叉查找树的性质继续向下走

同样注意在向右走的时候要更新x

int arank(int x)//查询排名为x的数是什么
{
int now=root;
while(1)
{
int used=tree[now].sum-tree[tree[now].ch[1]].sum;
if(x>tree[tree[now].ch[0]].sum&&x<=used) break;
if(x<used) now=tree[now].ch[0];
else x=x-used,now=tree[now].ch[1];
}
splay(now,root);
return tree[now].v;
}

求x的前驱

这个更容易,我们可以维护一个ans变量,然后对整棵树进行遍历,同时更新ans

int lower(int v)// 小于v的最大值
{
int now=root;
int ans=-maxn;
while(now)
{
if(tree[now].v<v&&tree[now].v>ans) ans=tree[now].v;
if(v>tree[now].v) now=tree[now].ch[1];
else now=tree[now].ch[0];
}
return ans;
}

求x的后继

这个和上一个一样,就不细讲了

int upper(int v)
{
int now=root;
int ans=maxn;
while(now)
{
if(tree[now].v>v&&tree[now].v<ans) ans=tree[now].v;
if(v<tree[now].v) now=tree[now].ch[0];
else now=tree[now].ch[1];
}
return ans;
}

完整代码:

// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ls(x) T[x].ch[0]
#define rs(x) T[x].ch[1]
#define fa(x) T[x].fa
#define root T[0].ch[1]
using namespace std;
const int MAXN=1e5+10,mod=10007,INF=1e9+10;
inline char nc()
{
static char buf[MAXN],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin)),p1==p2?EOF:*p1++;
}
inline int read()
{
char c=nc();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
return x*f;
}
struct node
{
int fa,ch[2],val,rec,sum;
}T[MAXN];
int tot=0,pointnum=0;
void update(int x){T[x].sum=T[ls(x)].sum+T[rs(x)].sum+T[x].rec;}
int ident(int x){return T[fa(x)].ch[0]==x?0:1;}
void connect(int x,int fa,int how){T[fa].ch[how]=x;T[x].fa=fa;}
void rotate(int x)
{
int Y=fa(x),R=fa(Y);
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^1],Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
update(Y);update(x);
}
void splay(int x,int to)
{
to=fa(to);
while(fa(x)!=to)
{
int y=fa(x);
if(T[y].fa==to) rotate(x);
else if(ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
}
int newnode(int v,int f)
{
T[++tot].fa=f;
T[tot].rec=T[tot].sum=1;
T[tot].val=v;
return tot;
}
void Insert(int x)
{
int now=root;
if(root==0) {newnode(x,0);root=tot;}//
else
{
while(1)
{
T[now].sum++;
if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
int nxt=x<T[now].val?0:1;
if(!T[now].ch[nxt])
{
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root);return ;
}
now=T[now].ch[nxt];
}
}
}
int find(int x)
{
int now=root;
while(1)
{
if(!now) return 0;
if(T[now].val==x) {splay(now,root);return now;}
int nxt=x<T[now].val?0:1;
now=T[now].ch[nxt];
}
}
void delet(int x)
{
int pos=find(x);
if(!pos) return ;
if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;}
else
{
if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
else
{
int left=T[pos].ch[0];
while(T[left].ch[1]) left=T[left].ch[1];
splay(left,T[pos].ch[0]);
connect(T[pos].ch[1],left,1);
connect(left,0,1);//
update(left);
}
}
}
int rak(int x)
{
int now=root,ans=0;
while(1)
{
if(T[now].val==x) return ans+T[T[now].ch[0]].sum+1;
int nxt=x<T[now].val?0:1;
if(nxt==1) ans=ans+T[T[now].ch[0]].sum+T[now].rec;
now=T[now].ch[nxt];
}
}
int kth(int x)//排名为x的数
{
int now=root;
while(1)
{
int used=T[now].sum-T[T[now].ch[1]].sum;
if(T[T[now].ch[0]].sum<x&&x<=used) {splay(now,root);return T[now].val;}
if(x<used) now=T[now].ch[0];
else now=T[now].ch[1],x-=used;
}
}
int lower(int x)
{
int now=root,ans=-INF;
while(now)
{
if(T[now].val<x) ans=max(ans,T[now].val);
int nxt=x<=T[now].val?0:1;//这里需要特别注意
now=T[now].ch[nxt];
}
return ans;
}
int upper(int x)
{
int now=root,ans=INF;
while(now)
{
if(T[now].val>x) ans=min(ans,T[now].val);
int nxt=x<T[now].val?0:1;
now=T[now].ch[nxt];
}
return ans;
}
int main()
{
#ifdef WIN32
freopen("a.in","r",stdin);
#else
#endif
int N=read();
while(N--)
{
int opt=read(),x=read();
if(opt==1) Insert(x);
else if(opt==2) delet(x);
else if(opt==3) printf("%d\n",rak(x));
else if(opt==4) printf("%d\n",kth(x));
else if(opt==5) printf("%d\n",lower(x));
else if(opt==6) printf("%d\n",upper(x));
}
return 0;
}

至此,splay最常用的几种函数就解决了,

下面来看几道裸题

例题

不知道为什么,我的splay跑的特别快,可能是脸太好了吧

splay详解(二)的更多相关文章

  1. .NET DLL 保护措施详解(二)关于性能的测试

    先说结果: 加了缓存的结果与C#原生代码差异不大了 我对三种方式进行了测试: 第一种,每次调用均动态编译 第二种,缓存编译好的对象 第三种,直接调用原生C#代码 .net dll保护系列 ------ ...

  2. PopUpWindow使用详解(二)——进阶及答疑

      相关文章:1.<PopUpWindow使用详解(一)——基本使用>2.<PopUpWindow使用详解(二)——进阶及答疑> 上篇为大家基本讲述了有关PopupWindow ...

  3. Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

    [Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.And ...

  4. logback -- 配置详解 -- 二 -- <appender>

    附: logback.xml实例 logback -- 配置详解 -- 一 -- <configuration>及子节点 logback -- 配置详解 -- 二 -- <appen ...

  5. 爬虫入门之urllib库详解(二)

    爬虫入门之urllib库详解(二) 1 urllib模块 urllib模块是一个运用于URL的包 urllib.request用于访问和读取URLS urllib.error包括了所有urllib.r ...

  6. [转]文件IO详解(二)---文件描述符(fd)和inode号的关系

    原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...

  7. Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

  8. HTTPS详解二:SSL / TLS 工作原理和详细握手过程

    HTTPS 详解一:附带最精美详尽的 HTTPS 原理图 HTTPS详解二:SSL / TLS 工作原理和详细握手过程 在上篇文章HTTPS详解一中,我已经为大家介绍了 HTTPS 的详细原理和通信流 ...

  9. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  10. pika详解(二) BlockingConnection

    pika详解(二) BlockingConnection   本文链接:https://blog.csdn.net/comprel/article/details/94592348 版权 Blocki ...

随机推荐

  1. SSL/TLS握手过程

    ----------------------------------专栏导航----------------------------------HTTPS协议详解(一):HTTPS基础知识 HTTPS ...

  2. 多媒体文件格式(二):FLV 格式

    在网络的直播与点播场景中,FLV也是一种常见的格式,FLV是Adobe发布的一种可以作为直播也可以作为点播的封装格式,其封装格式非常简单,均以FLVTAG的形式存在,并且每一个TAG都是独立存在的,接 ...

  3. [Swift]LeetCode832. 翻转图像 | Flipping an Image

    Given a binary matrix A, we want to flip the image horizontally, then invert it, and return the resu ...

  4. [Swift]LeetCode858. 镜面反射 | Mirror Reflection

    There is a special square room with mirrors on each of the four walls.  Except for the southwest cor ...

  5. 使用jQuery获取元素的宽度或高度的几种情况

    今天说说使用jQuery获取元素大小的遇到几种情况 使用jQuery获取元素的宽度或高度的有几种情况: 1.使用width(),它只能获取当前元素的内容的宽度: 2.使用innerWidth(),它只 ...

  6. 面向对象-Java MOOC翁恺老师第一次作业

    由于看这个慕课的时候已经结课了,没有办法提交查看代码是否正确...先保存一下,以后再提交改错 欢迎批评指正! 题目链接:https://www.icourse163.org/learn/ZJU-100 ...

  7. 分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?

    导读 在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC.3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见

  8. ubuntu 下安装docker 踩坑记录

    ubuntu 下安装docker 踩坑记录 # Setp : 移除旧版本Docker sudo apt-get remove docker docker-engine docker.io # Step ...

  9. qt deleterLater

    原文链接:浅谈 Qt 内存管理     Qt 内存管理是本文将要介绍的内容,在QT的程序中经常会看到只有new而不delete的情况,其实是因为QT有一套回收内存的机制,主要的规则如下: 1.所有继承 ...

  10. asp.net core 系列 4 注入服务的生存期

    一.服务的生存期 在容器中每个注册的服务,根据程序应用需求都可以选择合适的服务生存期,ASP.NET Core 服务有三种生存期配置: (1) Transient:暂时生存期,在每次请求时被创建. 这 ...