Treap是什么
树堆,在数据结构中也称
T
r
e
a
p
Treap
Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为
O
(
l
o
g
n
)
O(logn)
O(logn)。相对于其他的平衡二叉搜索树,
T
r
e
a
p
Treap
Treap的特点是实现简单,且能基本实现随机平衡的结构。
T
r
e
a
p
Treap
Treap是一棵二叉排序树,它的左子树和右子树分别是一个
T
r
e
a
p
Treap
Treap,和一般的二叉排序树不同的是,
T
r
e
a
p
Treap
Treap纪录一个额外的数据,就是优先级。
T
r
e
a
p
Treap
Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设父节点优先级的值要小于孩子节点优先级的值)。但是这里要注意的是
T
r
e
a
p
Treap
Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而
T
r
e
a
p
Treap
Treap可以并不一定是。
基本结构
class Treap
{
public:
int rt;
private:
int ans,sz;
vector<int> lson,rson,val,rnd,siz,eql;
}
r
t
rt
rt代表
T
r
e
a
p
Treap
Treap的树根,是一个非负整数,0代表空树。
a
n
s
ans
ans在查找前驱后继时有用,详见后文。
s
z
sz
sz等于整棵树的元素个数。
l
s
o
n
i
、
r
s
o
n
i
lson_i、rson_i
lsoni、rsoni分别代表节点
i
i
i的左右儿子编号。
v
a
l
i
、
r
n
d
i
val_i、rnd_i
vali、rndi分别代表节点
i
i
i的值
k
e
y
key
key和优先级
p
r
i
o
r
i
t
y
priority
priority。
s
i
z
i
siz_i
sizi等于以
i
i
i为根的
T
r
e
a
p
Treap
Treap的节点个数。
e
q
l
i
eql_i
eqli代表等于
v
a
l
i
val_i
vali的元素个数。
基本操作
T r e a p Treap Treap可以在 O ( h ) O(h) O(h)复杂度下实现以下操作:
- 插入数x。
- 删除数x,若有多个相同的x,则只删除1个。
- 查询数x的排名,若有多个相同的x,则输出最小的排名。举个例子,比如一共有1、2、3、3、5这五个数,则3的排名为3。
- 查询排名为x的数。
- 求x的前趋,即小于x且最大的数。
- 求x的后继,即大于x且最小的数。
下面将一一讲解以上操作的实现以及一些辅助函数。
初始化
此处 T r e a p Treap Treap的实现没有采用指针,所以应该在初始化时传入元素个数(实际上由于删除节点时没有回收编号,此处传入的值要自己好好算一下)以便于初始化 v e c t o r vector vector。
Treap::Treap(int n):rt(0),sz(0),ans(0)
{
++n;
lson.resize(n),rson.resize(n),val.resize(n),rnd.resize(n),siz.resize(n),eql.resize(n);
srand(time(NULL));
}
修改某个节点的siz
这是辅助函数之一,用来修改某个节点的 s i z siz siz,根据定义我们知道 s i z [ i ] = s i z [ l s o n [ i ] ] + s i z [ r s o n [ i ] ] + e q l [ i ] siz[i]=siz[lson[i]]+siz[rson[i]]+eql[i] siz[i]=siz[lson[i]]+siz[rson[i]]+eql[i]。即以 i i i为根的 T r e a p Treap Treap的元素个数等于左右子树元素个数之和加上这个节点的元素个数。
void Treap::modifySize(int idx)
{
siz[idx]=siz[lson[idx]]+siz[rson[idx]]+eql[idx];
}
左旋
话不多说,直接看图:
void Treap::leftRotate(int &idx)
{
int r=rson[idx];
rson[idx]=lson[r];
lson[r]=idx;
siz[r]=siz[idx];
modifySize(idx);
idx=r;
}
注意最后一步 i d x = r idx=r idx=r,因为经过左旋后,新的根节点就是 r r r了,所以我们应该令 i d x = r idx=r idx=r(注意参数是一个引用)。
右旋
和左旋恰好相反,借用一下百度百科的图吧:
void Treap::rightRotate(int &idx)
{
int l=lson[idx];
lson[idx]=rson[l];
rson[l]=idx;
siz[l]=siz[idx];
modifySize(idx);
idx=l;
}
插入数x
这是一个递归操作,如果我们到达了叶子节点,那么就新建一个节点,并修改对应的值;否则如果要插入的数 x x x等于当前节点的值 v a l [ i ] val[i] val[i],那么直接递增 e q l [ i ] eql[i] eql[i]即可;若 x < v a l [ i ] x<val[i] x<val[i],将其插入到左子树中,插入操作完成后,需要判断左儿子和自己的优先级,若不满足则需要进行一次右旋操作;若 x > v a l [ i ] x>val[i] x>val[i],操作和上面的类似,只不过要反着来。
void Treap::insert(int &idx,int v)
{
if(!idx)
{
idx=++sz;
siz[idx]=eql[idx]=1;
val[idx]=v;
rnd[idx]=rand();
return;
}
siz[idx]++;
if(val[idx]==v)
++eql[idx];
else if(val[idx]<v)
{
insert(rson[idx],v);
if(rnd[rson[idx]]<rnd[idx])
leftRotate(idx);
}
else
{
insert(lson[idx],v);
if(rnd[lson[idx]]<rnd[idx])
rightRotate(idx);
}
}
删除数x
这也是一个递归操作,如果到达叶子节点,函数结束;如果要删除的数
x
x
x等于当前节点的值
v
a
l
[
i
]
val[i]
val[i],且当
e
q
l
[
i
]
>
1
eql[i]>1
eql[i]>1,那么递减
s
i
z
[
i
]
、
e
q
l
[
i
]
siz[i]、eql[i]
siz[i]、eql[i]然后函数结束,否则说明当前节点的
e
q
l
=
1
eql=1
eql=1,我们需要删除这个节点,这时要讨论左右儿子的关系,如果左儿子为空,那么直接把右儿子提上来;如果右儿子为空,那么直接把左儿子提上来;否则需要比较左右儿子的优先级,把值较小的那个上来(通过左旋右旋),然后再进入对应的子树继续删除操作;如果
x
<
v
a
l
[
i
]
x<val[i]
x<val[i],递减
s
i
z
[
i
]
siz[i]
siz[i],进入左子树;如果
x
>
v
a
l
[
i
]
x>val[i]
x>val[i],递减
s
i
z
[
i
]
siz[i]
siz[i],进入右子树。
从上述叙述可以看出,删除操作其实是通过不断的左旋右旋把目标节点旋转到叶节点上,然后将其删除。
void Treap::del(int &idx,int v)
{
if(!idx)
return;
if(val[idx]==v)
{
if(eql[idx]>1)
{
--siz[idx],--eql[idx];
return;
}
if(!lson[idx]||!rson[idx])
idx=lson[idx]+rson[idx];
else if(rnd[lson[idx]]<rnd[rson[idx]])
{
rightRotate(idx);
del(idx,v);
}
else
{
leftRotate(idx);
del(idx,v);
}
}
else if(val[idx]<v)
{
--siz[idx];
del(rson[idx],v);
}
else
{
--siz[idx];
del(lson[idx],v);
}
}
查找数x的排名
查找给定的数 x x x的排名。如果 x x x等于当前节点的值 v a l [ i ] val[i] val[i],那么它的排名就等于 s i z [ l s o n [ i ] ] + 1 siz[lson[i]]+1 siz[lson[i]]+1;如果 x < v a l [ i ] x<val[i] x<val[i],那么递归进入左子树查询;如果 x > v a l [ i ] x>val[i] x>val[i],那么它的排名等于 s i z [ l s o n [ i ] ] + s q l [ i ] + siz[lson[i]]+sql[i]+ siz[lson[i]]+sql[i]+查询右子树的返回值。
int Treap::queryRank(int idx,int v)
{
if(!idx)
return 0;
if(val[idx]==v)
return siz[lson[idx]]+1;
else if(val[idx]<v)
return siz[lson[idx]]+eql[idx]+queryRank(rson[idx],v);
else
return queryRank(lson[idx],v);
}
查找排名为x的数
如果 x < = s i z [ l s o n [ i ] ] x<=siz[lson[i]] x<=siz[lson[i]],那么递归进入左子树查询;如果 x > s i z [ l s o n [ i ] ] + e q l [ i ] x>siz[lson[i]]+eql[i] x>siz[lson[i]]+eql[i],那么递归进入右子树查询,不过此时应该查询排名为 x − s i z [ l s o n [ i ] ] − e q l [ i ] x-siz[lson[i]]-eql[i] x−siz[lson[i]]−eql[i]的数;否则说明 v a l [ i ] val[i] val[i]就是排名为 x x x的数。
int Treap::queryNum(int idx,int rk)
{
if(!idx)
return 0;
if(rk<=siz[lson[idx]])
return queryNum(lson[idx],rk);
else if(rk>siz[lson[idx]]+eql[idx])
return queryNum(rson[idx],rk-siz[lson[idx]]-eql[idx]);
return val[idx];
}
查找数x的前趋
当 v a l [ i ] < x val[i]<x val[i]<x时,一个可能的答案是 i i i,为了找到更大的答案,我们要进入右子树继续查找;当 v a l [ i ] > = x val[i]>=x val[i]>=x时, i i i以及它的右子树不满足题意,要进入左子树查找。
void Treap::_queryPre(int idx,int v)
{
if(!idx)
return;
if(val[idx]<v)
{
ans=idx;
_queryPre(rson[idx],v);
}
else
_queryPre(lson[idx],v);
}
int Treap::queryPre(int idx,int v)
{
ans=0;
_queryPre(idx,v);
return val[ans];
}
查找数x的后继
当 v a l [ i ] > x val[i]>x val[i]>x时,一个可能的答案是 i i i,为了找到更小的答案,要进入左子树查找;当 v a l [ i ] < = x val[i]<=x val[i]<=x时, i i i以及它的左子树不满足题意,要进入右子树查找。
void Treap::_querySub(int idx,int v)
{
if(!idx)
return;
if(val[idx]>v)
{
ans=idx;
_querySub(lson[idx],v);
}
else
_querySub(rson[idx],v);
}
int Treap::querySub(int idx,int v)
{
ans=0;
_querySub(idx,v);
return val[ans];
}
完整模板
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
class Treap
{
public:
int rt;
Treap(int n);
void insert(int &idx,int v);
void del(int &idx,int val);
int queryRank(int idx,int v);
int queryNum(int idx,int rk);
int queryPre(int idx,int v);
int querySub(int idx,int v);
private:
int ans,sz;
vector<int> lson,rson,val,rnd,siz,eql;
void modifySize(int idx);
void leftRotate(int &idx);
void rightRotate(int &idx);
void _queryPre(int idx,int v);
void _querySub(int idx,int v);
};
Treap::Treap(int n):rt(0),sz(0),ans(0)
{
++n;
lson.resize(n),rson.resize(n),val.resize(n),rnd.resize(n),siz.resize(n),eql.resize(n);
srand(time(NULL));
}
void Treap::modifySize(int idx)
{
siz[idx]=siz[lson[idx]]+siz[rson[idx]]+eql[idx];
}
void Treap::leftRotate(int &idx)
{
int r=rson[idx];
rson[idx]=lson[r];
lson[r]=idx;
siz[r]=siz[idx];
modifySize(idx);
idx=r;
}
void Treap::rightRotate(int &idx)
{
int l=lson[idx];
lson[idx]=rson[l];
rson[l]=idx;
siz[l]=siz[idx];
modifySize(idx);
idx=l;
}
void Treap::insert(int &idx,int v)
{
if(!idx)
{
idx=++sz;
siz[idx]=eql[idx]=1;
val[idx]=v;
rnd[idx]=rand();
return;
}
siz[idx]++;
if(val[idx]==v)
++eql[idx];
else if(val[idx]<v)
{
insert(rson[idx],v);
if(rnd[rson[idx]]<rnd[idx])
leftRotate(idx);
}
else
{
insert(lson[idx],v);
if(rnd[lson[idx]]<rnd[idx])
rightRotate(idx);
}
}
void Treap::del(int &idx,int v)
{
if(!idx)
return;
if(val[idx]==v)
{
if(eql[idx]>1)
{
--siz[idx],--eql[idx];
return;
}
if(!lson[idx]||!rson[idx])
idx=lson[idx]+rson[idx];
else if(rnd[lson[idx]]<rnd[rson[idx]])
{
rightRotate(idx);
del(idx,v);
}
else
{
leftRotate(idx);
del(idx,v);
}
}
else if(val[idx]<v)
{
--siz[idx];
del(rson[idx],v);
}
else
{
--siz[idx];
del(lson[idx],v);
}
}
int Treap::queryRank(int idx,int v)
{
if(!idx)
return 0;
if(val[idx]==v)
return siz[lson[idx]]+1;
else if(val[idx]<v)
return siz[lson[idx]]+eql[idx]+queryRank(rson[idx],v);
else
return queryRank(lson[idx],v);
}
int Treap::queryNum(int idx,int rk)
{
if(!idx)
return 0;
if(rk<=siz[lson[idx]])
return queryNum(lson[idx],rk);
else if(rk>siz[lson[idx]]+eql[idx])
return queryNum(rson[idx],rk-siz[lson[idx]]-eql[idx]);
return val[idx];
}
void Treap::_queryPre(int idx,int v)
{
if(!idx)
return;
if(val[idx]<v)
{
ans=idx;
_queryPre(rson[idx],v);
}
else
_queryPre(lson[idx],v);
}
int Treap::queryPre(int idx,int v)
{
ans=0;
_queryPre(idx,v);
return val[ans];
}
void Treap::_querySub(int idx,int v)
{
if(!idx)
return;
if(val[idx]>v)
{
ans=idx;
_querySub(lson[idx],v);
}
else
_querySub(rson[idx],v);
}
int Treap::querySub(int idx,int v)
{
ans=0;
_querySub(idx,v);
return val[ans];
}