放在前面的话

本蒟蒻因为最近的题目总是搞点奇奇怪怪的平衡树,就去学了下\(Treap\)

现在来总结一下

由于本人是个蒟蒻,本文可能有部分错误,麻烦各位读者大佬在评论区提醒

什么是\(Treap\)

\(Treap\)取自两个单词,一是\(tree\),一是\(heap\)

也就是说,\(Treap\)结合了二叉搜索树和堆

\(Treap\)维持平衡的方法

方法就是

随 机 数!!!

不要不信,真的是随机数

对于每个点,\(Treap\)给予它们一个随机数

并要求在满足二叉搜索树的基础上,随机数要形成一个大(小)根堆


接下来将给出一道模板题,\(Treap\)的操作将在模板题的讲解中给出

例题讲解

放例题

\(Treap\)例题

讲解

数组

\(size[i]\)表示以\(i\)为根的子树的大小

\(num[i]\)表示值为\(i\)的个数

\(val[i]\)表示节点\(i\)的值

\(son[i][0/1]\)表示\(i\)的左/右儿子

\(rd[i]\)表示节点\(i\)的随机值

这些数组在接下来会多次提及,各位读者大佬可以稍稍记忆一下

操作

统计(\(pushup\))

重新统计以\(i\)为根的子树的大小

当前大小:左儿子的大小+右儿子的大小+当前这个数的个数

void pushup(int p)
{
size[p]=size[son[p][0]]+size[son[p][1]]+num[p];
}

旋转(\(rot\))!!!

\(Treap\)的核心操作

分为左旋和右旋,但是思路一样,故一起介绍

旋转的目的是将一个子节点移到父亲处,在过程中满足二叉搜索树的性质

以右旋为例

一开始是这样的



现在我们要将\(B\)转到\(A\)那里

怎么搞呢???

根据二叉搜索树的性质我们知道

\(B<A\)

那么可以把\(A\)丢给\(B\)当右儿子

但是\(B\)已经有了右儿子\(y\)了

再想,根据二叉搜索树有\(B<y<A\)

那么\(y\)就可以丢给\(A\)当左儿子

然后\(B\)的左儿子和\(A\)的右儿子不变

旋转完了之后



检查一下大小关系

旋转前:\(x<B<y<A<z\)

旋转后:\(x<B<y<A<z\)

一模一样

具体操作呢

上代码

void rot(int &p,int d)
{
int k=son[p][d^1];
son[p][d^1]=son[k][d];
son[k][d]=p;
pushup(p);
pushup(k);
p=k;
}

\(d\)为0是左旋,为1右旋

以\(d=1\)为例(右旋)

     a
/ \
b z
/ \
x y

\(k\)为\(p\)的左儿子

先把\(k\)的右儿子丢给\(p\)当左儿子:

son[p][d^1]=son[k][d];

现在长这样

              a(p)
/ \
b(k) y z
/
x

再把\(p\)丢给\(k\)当右儿子

son[k][d]=p;

变成了这样

     b(k)
/ \
x a(p)
/ \
y z

再\(pushup(p和k)\)

最后\(p=k\)

结束

那么我们就可以用旋转来维护堆了

插入(\(ins\))

要插入一个数\(x\)

可以一直判断\(x\)与当前节点的大小关系,选择往哪边递归

直到找到一棵空子树就把\(x\)放进去

放进去之后看一下\(rd\)值的大小,有不对的就旋转

插入后重新统计大小

void ins(int &p,int x)
{
if (!p)
{
sum++;
p=sum;
size[p]=num[p]=1;
val[p]=x;
rd[p]=rand();
return;
}
if (val[p]==x)
{
num[p]++;
size[p]++;
return;
}
int d=(x>val[p]);
ins(son[p][d],x);
if (rd[p]<rd[son[p][d]]) rot(p,d^1);
pushup(p);
}

删除(\(del\))

跟插入差不多

\(x<val[p]\)往左边走

\(x>val[p]\)往右边走

有点不同的是在\(x=val[p]\)的时候

分4种情况讨论

  1. 无左儿子和右儿子
  2. 无左儿子
  3. 无右儿子
  4. 有左儿子和右儿子

情况1:删自己

情况2:左旋,往左边走

情况3:右旋,往右边走

情况4:看哪边的\(rd\)值大,就旋转哪边,往那边走

删除完之后重新统计一下大小

void del(int &p,int x)
{
if (!p) return;
if (x<val[p]) del(son[p][0],x);
else if (x>val[p]) del(son[p][1],x);
else
{
if (!son[p][0]&&!son[p][1])
{
num[p]--;
size[p]--;
if (!num[p]) p=0;
}
else if (!son[p][1])
{
rot(p,1);
del(son[p][1],x);
}
else if (!son[p][0])
{
rot(p,0);
del(son[p][0],x);
}
else
{
int d=(rd[son[p][0]]>rd[son[p][1]]);
rot(p,d);
del(son[p][d],x);
}
}
pushup(p);
}

查询排名(\(get\_rank\))

如果没有这个数,返回0

如果\(val[p]=x\),输出左儿子的大小+1

如果\(val[p]>x\),往左儿子走

如果\(val[p]<x\),往右儿子走,并输出左儿子的大小+\(num[x]\)+\(x\)在右儿子中的排名

int get_rank(int p,int x)
{
if (!p) return 0;
if (val[p]==x) return (size[son[p][0]]+1);
if (val[p]<x) return (size[son[p][0]]+num[p]+get_rank(son[p][1],x));
if (val[p]>x) return get_rank(son[p][0],x);
}

查询值(\(get\_sum\))

如果\(size[son[p][0]>x\) 往左边走

如果\(size[son[p][0]+num[p]<x\) 往右边走,在右边查找排名\(x-size[son[p][0]-num[p]\)的数

若都不满足,返回\(val[p]\)

int get_sum(int p,int x)
{
if (!p) return 0;
if (size[son[p][0]]>=x) return get_sum(son[p][0],x);
else if (size[son[p][0]]+num[p]<x) return get_sum(son[p][1],x-size[son[p][0]]-num[p]);
else return val[p];
}

查询前驱(\(get\_pre\))

如果当前\(p\)为0返回\(-∞\)(一定要特别小)

如果\(val[p]>=x\),即在左儿子中,那就往左边走

否则的话返回当前值和右儿子中的前驱里大的那个(所以为什么要特别小)

int get_pre(int p,int x)
{
if (!p) return -inf;
if (val[p]>=x) return get_pre(son[p][0],x);
else return max(val[p],get_pre(son[p][1],x));
}

查询后继(\(get\_suc\))

跟查询前驱类似

只不过为0的时候返回\(∞\),因为后面是\(min\)

左儿子和右儿子换一下就可以

int get_suc(int p,int x)
{
if (!p) return inf;
if (val[p]<=x) return get_suc(son[p][1],x);
else return min(val[p],get_suc(son[p][0],x));
}

到此所有的操作都已讲解完毕

Code——总

#include<cstdio>
#include<stdlib.h>
#include<iostream>
#define inf 2147483647
using namespace std;
int n,pd,x,s,sum,size[100005],son[100005][3],val[100005],num[1000005],rd[100005];
void pushup(int p)
{
size[p]=size[son[p][0]]+size[son[p][1]]+num[p];
}
void rot(int &p,int d)
{
int k=son[p][d^1];
son[p][d^1]=son[k][d];
son[k][d]=p;
pushup(p);
pushup(k);
p=k;
}
void ins(int &p,int x)
{
if (!p)
{
sum++;
p=sum;
size[p]=num[p]=1;
val[p]=x;
rd[p]=rand();
return;
}
if (val[p]==x)
{
num[p]++;
size[p]++;
return;
}
int d=(x>val[p]);
ins(son[p][d],x);
if (rd[p]<rd[son[p][d]]) rot(p,d^1);
pushup(p);
}
void del(int &p,int x)
{
if (!p) return;
if (x<val[p]) del(son[p][0],x);
else if (x>val[p]) del(son[p][1],x);
else
{
if (!son[p][0]&&!son[p][1])
{
num[p]--;
size[p]--;
if (!num[p]) p=0;
}
else if (!son[p][1])
{
rot(p,1);
del(son[p][1],x);
}
else if (!son[p][0])
{
rot(p,0);
del(son[p][0],x);
}
else
{
int d=(rd[son[p][0]]>rd[son[p][1]]);
rot(p,d);
del(son[p][d],x);
}
}
pushup(p);
}
int get_rank(int p,int x)
{
if (!p) return 0;
if (val[p]==x) return (size[son[p][0]]+1);
if (val[p]<x) return (size[son[p][0]]+num[p]+get_rank(son[p][1],x));
if (val[p]>x) return get_rank(son[p][0],x);
}
int get_sum(int p,int x)
{
if (!p) return 0;
if (size[son[p][0]]>=x) return get_sum(son[p][0],x);
else if (size[son[p][0]]+num[p]<x) return get_sum(son[p][1],x-size[son[p][0]]-num[p]);
else return val[p];
}
int get_pre(int p,int x)
{
if (!p) return -inf;
if (val[p]>=x) return get_pre(son[p][0],x);
else return max(val[p],get_pre(son[p][1],x));
}
int get_suc(int p,int x)
{
if (!p) return inf;
if (val[p]<=x) return get_suc(son[p][1],x);
else return min(val[p],get_suc(son[p][0],x));
}
int main()
{
freopen("104.in","r",stdin);
scanf("%d",&n);
while (n--)
{
scanf("%d%d",&pd,&x);
if (pd==1) ins(s,x);
if (pd==2) del(s,x);
if (pd==3) printf("%d\n",get_rank(s,x));
if (pd==4) printf("%d\n",get_sum(s,x));
if (pd==5) printf("%d\n",get_pre(s,x));
if (pd==6) printf("%d\n",get_suc(s,x));
}
return 0;
}

教学之Treap的更多相关文章

  1. Yeoman 官网教学案例:使用 Yeoman 构建 WebApp

    STEP 1:设置开发环境 与yeoman的所有交互都是通过命令行.Mac系统使用terminal.app,Linux系统使用shell,windows系统可以使用cmder/PowerShell/c ...

  2. Linux实战教学笔记08:Linux 文件的属性(上半部分)

    第八节 Linux 文件的属性(上半部分) 标签(空格分隔):Linux实战教学笔记 第1章 Linux中的文件 1.1 文件属性概述(ls -lhi) linux里一切皆文件 Linux系统中的文件 ...

  3. Linux实战教学笔记07:Linux系统目录结构介绍

    第七节 Linux系统目录结构介绍 标签(空格分隔):Linux实战教学笔记 第1章 前言 windows目录结构 C:\windows D:\Program Files E:\你懂的\精品 F:\你 ...

  4. Linux实战教学笔记06:Linux系统基础优化

    第六节 Linux系统基础优化 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 基础环境 第2章 使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 第一步:先备份 ...

  5. Linux实战教学笔记05:远程SSH连接服务与基本排错(新手扫盲篇)

    第五节 远程SSH连接服务与基本排错 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 远程连接LInux系统管理 1.1 为什么要远程连接Linux系统 在实际的工作场景中,虚拟机界面或物理 ...

  6. Linux实战教学笔记04:Linux命令基础

    第四节:Linux命令基础 标签(空格分隔):Linux实战教学笔记 第1章 认识操作环境 root:当前登陆的用户名 @分隔符 chensiqi:主机名 -:当前路径位置 用户的提示符 1.1 Li ...

  7. Linux实战教学笔记03:操作系统发展历程及系统版本选择

    标签(空格分隔): Linux实战教学笔记-陈思齐 第1章 Linux简介 1.1 什么是操作系统? 简单讲:操作系统就是一个人与计算机硬件的中介. 操作系统,英文名称Operating System ...

  8. Linux实战教学笔记02:计算机系统硬件核心知识

    标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 互联网企业常见服务器介绍 1.1 互联网公司服务器品牌 - DELL(大多数公司,常用) - HP - IBM(百度在用) 浪潮 联想 航天联 ...

  9. Linux实战教学笔记01:计算机硬件组成与基本原理

    标签(空格分隔): Linux实战教学笔记 第1章 如何学习Linux 要想学好任何一门学问,不仅要眼睛看,耳朵听,还要动手记,勤思考,多交流甚至尝试着去教会别人. 第2章 服务器 2.1 运维的基本 ...

随机推荐

  1. 【故障公告】Memcached 的“惹祸”,不知在为谁背锅

    在 .NET 5.0 背锅 . Memcached 的惹祸 .缓存雪崩之后,我们没有找到问题的真正原因,我们知道没有找到根源的故障总是会再次光临的,不是在这周就是在下周,也许就在双11前后. 就在今天 ...

  2. Pycharm激活码,最新2020Pycharm永久激活码!!!

    分享一个Pycharm激活码给各位,是一个永久的Pycharm激活码~ 要是下边的这个Pycharm激活码失效了的话,大家可以关注微信公众号:Python联盟,然后回复"激活码"即 ...

  3. 3.3 Spring5源码---循环依赖过程中spring读取不完整bean的最终解决方案

    根据之前解析的循环依赖的源码, 分析了一级缓存,二级缓存,三级缓存的作用以及如何解决循环依赖的. 然而在多线程的情况下, Spring在创建bean的过程中, 可能会读取到不完整的bean. 下面, ...

  4. python_socket_tcp_文件传输

    server.py import json import struct import socket # 接收 sk = socket.socket() # sk.bind(('127.0.0.1',9 ...

  5. mysql yyyy-MM-dd function UNIX_TIMESTAMP('yyyy-MM-dd HH:mm:ss')

    mysql yyyy-MM-dd function UNIX_TIMESTAMP('yyyy-MM-dd HH:mm:ss') select UNIX_TIMESTAMP('1997-10-04 22 ...

  6. linux: c语言 关闭标准输出STDOUT_FILENO对父子进程的影响

    简介标准 I/O 库(stdio)及其头文件 stdio.h 为底层 I/O 系统调用提供了一个通用的接口.这个库现在已经成为 ANSI 标准 C 的一部分.标准 I/O 库提供了许多复杂的函数用于格 ...

  7. netfilter内核态与用户态 通信 之 sockopt

    用户态与内核态交互通信的方法不止一种,sockopt是比较方便的一个,写法也简单.缺点就是使用 copy_from_user()/copy_to_user()完成内核和用户的通信, 效率其实不高, 多 ...

  8. Markdown文档示例

    目录 我是大标题,和一级标题长得一样 我是次级标题,和二级标题长得一样 这是一级标题 这是二级标题 一直到六级标题 列表 表格 分割线 字体 超链接 插入图片 引用 代码块 注意下面每一个标记如果和内 ...

  9. mysql参数总结

    1.innodb_old_blocks_pct 确定modpoint位置,默认37,(3/8=37%)可以通过这个调整young与old比. innodb_old_blocks_time:当有大的查询 ...

  10. 11Linux之软件包管理

    11Linux之软件包管理 目录 11Linux之软件包管理 11 软件包管理 11.1 软件包介绍 11.1.1 编程语言分类 11.1.2 三种安装包 11.2 rpm包管理 11.2.1 rpm ...