浅谈普通平衡树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. jQuery上传文件

    1.引入资源 <script src="/yami/backend/backres/js/jquery.min.js"></script> <scri ...

  2. Centos7 安装ELK日志分析

    1.安装前准备 借鉴:https://www.cnblogs.com/straycats/p/8053937.html 操作系统:Centos7 虚拟机  8G内存  jdk8+ 软件包下载:采用rp ...

  3. 20155239吕宇轩 Exp1 PC平台逆向破解(5)M

    20155239 网络对抗 Exp1 PC平台逆向破解(5)M 实验内容 (1).掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码(1分) (2)掌握反汇编与十六进制编程器 (1分) ...

  4. Luogu P1273 有线电视网

    最近写DP写得比较多了 但是POJ上的题目太傻比了而且不想看英文的题面,然后就在Luogu的试炼场里找了一个DP EX专题写了一下(大概3days吧,一天一题差不多) 这是一道比较简单的DP 话说树形 ...

  5. angularJs 技巧总结及最佳实践

    强烈建议通读官方wiki文档,里面包含了FAQ,最佳实践,深入理解最核心的Directive及Scope等文章, 基础 1. 使用ng-repeat指令,为防止重复值发生的错误.加上track by ...

  6. [CF1059E]Split the Tree[贪心+树上倍增]

    题意 给定 \(n\) 个节点的树,点有点权 \(w\) ,划分成多条儿子到祖先的链,要求每条链点数不超过 \(L\) ,和不超过 \(S\),求最少划分成几条链. \(n\leq 10^5\) . ...

  7. C# Language Specification 5.0 (翻译)第三章 基本概念

    应用程序启动 拥有进入点(entry point)的程序集称应用程序(application).当运行一应用程序时,将创建一新应用程序域(application domain).同一个应用程序可在同一 ...

  8. Activity猫的一生-故事解说Activity生命周期

    大家好,关于Android中Activity的生命周期,网上大多数文章基本都是直接贴图.翻译API,比较笼统含糊不清. 我就用故事来说一说: 有个人叫User,TA养了几只猫,有只猫叫Activity ...

  9. beef局域网内模拟攻击

    0x0环境 主机A win10:10.51.20.60(wifi) 主机A中的虚拟机kali(攻击者):192.168.110.129(NAT) 主机A中的虚拟机win2003(受害者):192.16 ...

  10. PHP学习 类型 变量 常数 运算符

    PHP支持下列8种类型 标量类型 scalar type整数 integer浮点数 float double布尔 boolean字符串 string 特殊类型 special typeNULL资源 r ...