浅谈普通平衡树Treap

平衡树,Treap=Tree+heap这是一个很形象的东西

我们要维护一棵树,它满足堆的性质和二叉查找树的性质(BST),这样的二叉树我们叫做平衡树

并且平衡树它的结构是接近于比较均衡的。

考虑Treap解决的问题:插入,删除,排名(排名为x的数,数x的排名)、前驱和后继

这里的英文函数名分别定义为insert(插入) erase(删除),rank(求数x的排名),find(求排名是x的数),pre(x的前驱),nex(x的后继)

1Treap节点的定义和意义

对于一个Treap的节点我们定义如下变量size\val\cnt\key\ch[0]\ch[1]

分别解释一下他们的含义:

数的个数和数的种类是不同的概念!

size:和树链剖分中size[]的含义一样这里的含义是节点u以下含u有多少数的个数

val:储存在节点u中,相同元素的值

cnt:储存在节点u中,相同元素的数的个数

key:一个随机数在平衡树中要求父亲的key值,小于两个儿子的key值用来保证树的平衡

ch[0]:左儿子

ch[1]:右儿子

20 rotate函数的解释*

(以右旋为例)

这是原来的树:

我们要把D点(是B的左儿子)右旋到根节点B

首先把D点和右节点G的边断,D和他父亲的边断。

然而如果我们把D变成子树的根的话由BST的性质得D<B,所以B一定是D的右子节点。

等会!那G怎么办?

分析G的大小: B>G>D刚好可以放在B的左子树,然后把BDE这棵子树整个的连到D上刚刚好吧!

其实真的比较清楚,就这么把左端点转到根节点了!

注意到我们这里是按照优先级的大小来确定转的方向从而确定出一个Heap(大根堆)的。

啊啊啊啊好容易找到两张动图GIF:

左旋:(右边儿子S上去,父亲E到左边)

右旋:(左边儿子E上去,父亲S到右边)

这样可以保证树的形态随机!

void rotate(int &x)//这是右旋
{
int son=t[x].ch[]; //son一直都是P
t[x].ch[]=t[son].ch[]; //Q的左节点为B
t[son].ch[]=x; //P的右节点是Q
up(x); up(x=son);//由于位置变化size和cnt都变化
}

请读者按照上图理解右旋的相关内容,并尝试写出左旋的代码:

void rotate(int &x)
{
int son=t[x].ch[];//son一直都是Q
t[x].ch[]=t[son].ch[];//P的右节点为B
t[son].ch[]=x;//Q的左节点是P
up(x); up(x=son);//更新
}

由此我们得到旋转一般写法:(d代表那个子节点(0左1右)想向上)

void rotate(int &x,int d)//包含左旋右旋d=1左旋(右子向上)d=0右旋(左子向上)
{
int son=t[x].ch[d];
t[x].ch[d]=t[son].ch[d^];
t[son].ch[d^]=x;
up(x); up(x=son);
}

30其他函数略谈

A.insert

void insert(int &x,int val)//插入节点
{
if (!x) { //此节点为空新开一个
x=++tot;
t[x].size=t[x].cnt=;
t[x].val=val;
t[x].key=rand();
return;
}
t[x].size++; //进过一次x一定在x的子树上所以size要++
if (t[x].val==val) { t[x].cnt++; return;} //等于就直接插入
int d=t[x].val<val; //当前节点的val小于要求的val往大的搜
insert(t[x].ch[d],val); //走对应的儿子
if (t[x].key>t[t[x].ch[d]].key) rotate(x,d); //必须满足父节点的key大于两个儿子
}

B.erase

void erase(int &x,int val)
{
if (!x) return;//节点为空跳过
if (t[x].val==val) { //找到了
if (t[x].cnt>) { t[x].cnt--; t[x].size--; return;} //大于一不删点
int d=t[ls].key>t[rs].key; //删点
if (ls==||rs==) x=ls+rs; //往有孩子的一个地方走
else rotate(x,d),erase(x,val); //先转一转把空节点放在下面,然后最终会被放在最底层
} else t[x].size--,erase(t[x].ch[t[x].val<val],val); //经过出一定在x子树中所以x的size要--,然后往二叉查找方向搜
}

C.rank和find

int rank(int x,int val){//找val的rank值
if (!x) return ; //没有返回0
if (t[x].val==val) return t[ls].size+; //找到当前根就是x那么就是比他小的数的数目size+1(排名在他们之后)
if (t[x].val>val) return rank(ls,val);//当前大了,那么往小的找
return t[x].cnt+t[ls].size+rank(rs,val); //当前小了说明左儿子的size和根节点的cnt都是比val小的要加上数的个数,然后往大的搜
}
int find(int rt,int k) //求rank=k的值是多少
{
int x=rt; //从根开始
while () {
if (k<=t[ls].size) x=ls; //当前的k比左子树的元素个数少了那么一定往左子树搜
else if (k>t[x].cnt+t[ls].size) k-=t[x].cnt+t[ls].size,x=rs;//如果当前的k比左子树数的个数和根节点的cnt之和还大那么就是在右子树里,减掉(t[x].cnt+t[ls].size)用新的k迭代(从另外一个根搜)
else return t[x].val;//否则就是找到了return就行
}

D.pre和nex

int pre(int x,int val)
{
if (!x) return -0x7f7f7f7f7f; //避免越界不让更新
if (t[x].val>=val) return pre(ls,val); //等号千万别拉下
return max(pre(rs,val),t[x].val);//以防搜不到
}
int nex(int x,int val)
{
if (!x) return 0x7f7f7f7f7f;//避免越界不让更新
if (t[x].val<=val) return nex(rs,val);//等号千万别拉下
return min(nex(ls,val),t[x].val);//以防搜不到
}

40模板题目

P3369 【模板】普通平衡树

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数)
  6. 求x的后继(后继定义为大于x,且最小的数)

输入输出格式

输入格式:

第一行为n,表示操作的个数,下面n行每行有两个数opt和x,opt表示操作的序号( 1≤opt≤6 )

输出格式:

对于操作3,4,5,6每行输出一个数,表示对应答案

输入输出样例

输入样例#1:

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
输出样例#1:

106465
84185
492737
输入样例#2: 

输出样例#2:

说明

时空限制:1000ms,128M

1.n的数据范围: n≤100000

2.每个数的数据范围:[−10^7,10^7]

# include <bits/stdc++.h>
using namespace std;
const int MAXN=;
int root=;int tot=;
inline int read()
{
int X=,w=;char c=;
while (!(c>=''&&c<='')) w|=c=='-',c=getchar();
while (c>=''&&c<='') X=(X<<)+(X<<)+(c^),c=getchar();
return w?-X:X;
}
inline void po(int x){
if (x<) { putchar('-');x=-x;}
if (x>) po(x/);
putchar(''+x%);
}
inline void print(int x){ po(x);putchar('\n');}
struct Treap{ int size,val,cnt,key,ch[];}t[MAXN];
#define ls t[x].ch[0]
#define rs t[x].ch[1]
void up(int x){t[x].size=t[x].cnt+t[ls].size+t[rs].size;}
void rotate(int &x,int d)
{
int son=t[x].ch[d];
t[x].ch[d]=t[son].ch[d^];
t[son].ch[d^]=x;
up(x); up(x=son);
}
void insert(int &x,int val)
{
if (!x) {
x=++tot;
t[x].size=t[x].cnt=;
t[x].val=val;
t[x].key=rand();
return;
}
t[x].size++;
if (t[x].val==val) { t[x].cnt++; return;}
int d=t[x].val<val;
insert(t[x].ch[d],val);
if (t[x].key>t[t[x].ch[d]].key) rotate(x,d);
}
void erase(int &x,int val)
{
if (!x) return;
if (t[x].val==val) {
if (t[x].cnt>) { t[x].cnt--; t[x].size--; return;}
int d=t[ls].key>t[rs].key;
if (ls==||rs==) x=ls+rs;
else rotate(x,d),erase(x,val);
} else t[x].size--,erase(t[x].ch[t[x].val<val],val);
}
inline int rank(int x,int val){
if (!x) return ;
if (t[x].val==val) return t[ls].size+;
if (t[x].val>val) return rank(ls,val);
return t[x].cnt+t[ls].size+rank(rs,val);
}
int find(int rt,int k)
{
int x=rt;
while () {
if (k<=t[ls].size) x=ls;
else if (k>t[x].cnt+t[ls].size) k-=t[x].cnt+t[ls].size,x=rs;
else return t[x].val;
}
}
inline int pre(int x,int val)
{
if (!x) return -0x7f7f7f7f7f;
if (t[x].val>=val) return pre(ls,val);
return max(pre(rs,val),t[x].val);
}
int nex(int x,int val)
{
if (!x) return 0x7f7f7f7f7f;
if (t[x].val<=val) return nex(rs,val);
return min(nex(ls,val),t[x].val);
}
int main()
{
srand(time(NULL)*);
int m=read(),opt,x;
tot=;
while (m--) {
opt=read();x=read();
switch (opt) {
case :insert(root,x);break;
case :erase(root,x);break;
case :print(rank(root,x));break;
case :print(find(root,x));break;
case :print(pre(root,x));break;
case :print(nex(root,x));break;
}
}
return ;
}

50注意点(容易码错的地方)

  • 在全局定义root变量初始化为0
  • rank函数请把return t[L].size+t[x].cnt+rank(R,val);语句放在最后不要放在中间!

60STL平衡树了解下

# include <bits/stdc++.h>
# define int long long
using namespace std;
int n;
struct STL_Treap{
vector<int>a;
void insert(int x) { a.insert(lower_bound(a.begin(),a.end(),x),x);}
void erase(int x) {a.erase(lower_bound(a.begin(),a.end(),x));}
int rank(int x) {return lower_bound(a.begin(),a.end(),x)-a.begin()+;}
int kth(int x){return a[x-];}
int pre(int x) {return *(--lower_bound(a.begin(),a.end(),x));}
int nxt(int x){return *upper_bound(a.begin(),a.end(),x);}
}treap;
signed main()
{
scanf("%lld",&n);
for (int i=;i<=n;i++) {
int a,b;
scanf("%lld%lld",&a,&b);
switch (a) {
case 1ll:treap.insert(b);break;
case 2ll:treap.erase(b);break;
case 3ll:cout<<treap.rank(b)<<'\n';break;
case 4ll:cout<<treap.kth(b)<<'\n';break;
case 5ll:cout<<treap.pre(b)<<'\n';break;
case 6ll:cout<<treap.nxt(b)<<'\n';break;
}
}
return ;
}

普通平衡树Treap(含旋转)学习笔记的更多相关文章

  1. Treap与fhq_Treap学习笔记

    1.普通Treap 通过左右旋来维护堆的性质 左右旋是不改变中序遍历的 #include<algorithm> #include<iostream> #include<c ...

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

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

  3. 平衡树学习笔记(2)-------Treap

    Treap 上一篇:平衡树学习笔记(1)-------简介 Treap是一个玄学的平衡树 为什么说它玄学呢? 还记得上一节说过每个平衡树都有自己的平衡方式吗? 没错,它平衡的方式是......rand ...

  4. 左偏树 / 非旋转treap学习笔记

    背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...

  5. 平衡树学习笔记(6)-------RBT

    RBT 上一篇:平衡树学习笔记(5)-------SBT RBT是...是一棵恐怖的树 有多恐怖? 平衡树中最快的♂ 不到200ms的优势,连权值线段树都无法匹敌 但是,通过大量百度,发现RBT的代码 ...

  6. 平衡树学习笔记(5)-------SBT

    SBT 上一篇:平衡树学习笔记(4)-------替罪羊树 所谓SBT,就是Size Balanced Tree 它的速度很快,完全碾爆Treap,Splay等平衡树,而且代码简洁易懂 尤其是插入节点 ...

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

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

  8. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

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

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

随机推荐

  1. CGAL学习:数据类型

    CGAL 4.13 - Number Types 1 Introduction(介绍:略) 涉及到的数大致有3种:一是整数,二是有理数,三是浮点数.有理数可以用2个整数表示.精度上可分为任意精度和固定 ...

  2. Spring + SpringMVC配置

    代码结构如下 web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xs ...

  3. 20155321 《网络攻防》 Exp3 免杀原理与实践

    20155321 <网络攻防> Exp3 免杀原理与实践 基础问题回答 杀软是如何检测出恶意代码的? 根据实验指导书,杀软有两个方法可以检测出恶意代码.第一种是基于特征码,即先对流行代码特 ...

  4. STM32之HAL库、标准外设库、LL库

    标准外设库(Standard Peripherals Library),应该是最早推出的版本,以前用STM32F103的时候,用的多 HAL(Hardware Abstraction Layer),硬 ...

  5. 洛咕 P2336 [SCOI2012]喵星球上的点名

    洛咕 P2336 [SCOI2012]喵星球上的点名 先求出SA和height,一个点名串对应的就是一段区间,还有很多个点,就转化成了 有很多个区间,很多个点集,对每个区间计算和多少个点集有交,对每个 ...

  6. idea 解决 pom.xml 中,maven仓库无法导入的问题(红线)

    只需要用另一篇文章的 maven clean install 功能就行了 idea Cannot Resolve Symbol 问题解决

  7. java监听器(Listener)学习笔记

    现在来说说Servlet的监听器Listener,它是实现了javax.servlet.ServletContextListener 接口的服务器端程序,它也是随web应用的启动而启动,只初始化一次, ...

  8. Vxlan抓包

    实验目的:验证Openstack  vxlan组网模式验证虚拟机数据是否通过物理网卡流出 一. 同网段不同主机间虚拟机通讯 (同网段通讯直接通过物理机隧道口链接对端物理机隧道口,不需要通过网络节点): ...

  9. 关于UGUI不拦截射线的方法

    起因:开发游戏,要在设置界面里给一个设置项添加一个东西解释这个项是干啥的,要求鼠标移到文字上的时候显示一个弹窗差不多的东西,见动图,鼠标移开会消失.但是当我移动鼠标到弹窗上的时候,UGUI会发射一根射 ...

  10. 复习下VLAN的知识

    转载:来自百度百科 VLAN一般指虚拟局域网 VLAN(Virtual Local Area Network)的中文名为"虚拟局域网". 虚拟局域网(VLAN)是一组逻辑上的设备和 ...