【学习笔记】平衡树 Treap
平衡树 旋转Treap
一个重要的等式
treap=tree+heap
解释:treap,即树堆,顾名思义,就是树+堆,而一般的,此处的树指BST(二叉搜索树)
也就是说,treap是一棵BST,也是一个二叉堆,但二者的性质似乎有些冲突啊,BST怎么可能同时是一个二叉堆呢?treap则是很好的解决了这一问题
我们可以给每个值一个随机附加的优先级,让值满足BST的性质(左<根<右),而优先级满足堆的性质
Treap的优点
Treap,作为BST和堆的结合体,一种随机化的数据结构,它的形态根本无法固定下来——不是规则的二叉树,更不是完全二叉树,甚至有时无法满足平衡树两侧高度差小于等于一的定义,但是它却可以保证log的时间复杂度,防止极端的情况使BST退化成链
平衡树の作用
模板题
写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入 \(x\) 数
- 删除 \(x\) 数(若有多个相同的数,因只删除一个)
- 查询 \(x\) 数的排名(排名定义为比当前数小的数的个数 \(+1\) )
- 查询排名为 \(x\) 的数
- 求 \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)
- 求 \(x\) 的后继(后继定义为大于 \(x\),且最小的数)
考虑如何针对Treap的性质来实现这些操作
当然是旋转啦
Treap的实现
先来看一下预处理操作
随机分配的优先级(这个背过就好)
inline int rand()
{
static int seed=12345;
return seed=(int)seed*482711ll%2147483647;
}
push_up 更新操作(类似BST,一目了然)
void push_up(int p)
{
t[p].siz=t[t[p].ls].siz+t[t[p].rs].siz+t[p].cnt;
}
然后就是旋转啦
想一下,我们要向满足Treap堆的性质,那么当其不满足堆性质时就应该有所作为,我们可以通过旋转来搞定
放一张图,来自这里
其实还是很好理解的,但是唯一迷惑的就是B变成了F的儿子,其实是因为旋转前后要保证BST遍历顺序不变,而大家都知道BST的遍历顺序是中序遍历,自己手模一遍就可以理解啦QWQ
左旋
void left_rotate(int &p) //左旋
{
int x=t[p].rs;
t[p].rs=t[x].ls;
t[x].ls=p;
t[x].siz=t[p].siz;
push_up(p);
p=x;
}
右旋
void right_rotate(int &p) //右旋
{
int x=t[p].ls;
t[p].ls=t[x].rs;
t[x].rs=p;
t[x].siz=t[p].siz;
push_up(p);
p=x;
}
插入操作
insert操作,我们可以利用动态开点的方法,同时也不断的进行比较(val)、旋转(根据优先级),直到该节点成为叶节点位置,至于往哪转的话自己根据代码手模一下即可
void insert(int &p,int x) //插入
{
if(p==0) //成功变成了叶节点
{
p=++tot;
t[p].siz=t[p].cnt=1;
t[p].val=x;
t[p].pri=rand();
return ;
}
t[p].siz++;
if(t[p].val==x) //search中。。。
{
t[p].cnt++;
}
else
{
if(x>t[p].val) //x应该在右子树
{
insert(t[p].rs,x); //把它插进右子树里
if(t[t[p].rs].pri<t[p].pri) //优先级比较(此处是小根堆)
{
left_rotate(p); //左旋p点,让右子树上去
}
}
else //x应该在左子树
{
insert(t[p].ls,x); //把它插进左子树里
if(t[t[p].ls].pri<t[p].pri) //同理
{
right_rotate(p); //右旋p点,让左子树上去
}
}
}
}
删除操作
remove操作,同时也是代码量最大的操作,可以根据BST性质删,但此处主要介绍根据堆的性质删除
若一节点左节点pri小于右节点,那就右旋该节点,让左节点上去(因为是小根堆),此时右节点变成了右子树的根节点,然后再访问右节点递归进行(因为树就是递归的操作啦),然后pri小的弄上去,保证堆的性质,然后进行删除
void remove(int &p,int x) //删除
{
if(p==0)
{
return ;
}
if(t[p].val==x)
{
if(t[p].cnt>1)
{
t[p].cnt--;
t[p].siz--;
}
else
{
if(t[p].ls==0 || t[p].rs==0)
{
p=t[p].ls+t[p].rs;
}
else
{
if(t[t[p].ls].pri<t[t[p].rs].pri)
{
right_rotate(p);
remove(p,x);
}
else
{
left_rotate(p);
remove(p,x);
}
}
}
}
else
{
if(x>t[p].val)
{
t[p].siz--;
remove(t[p].rs,x);
}
else
{
t[p].siz--;
remove(t[p].ls,x);
}
}
}
查询排名 && 第k值
这个很好理解啦(cnt指的是这样的值有多少个)
int query_rank(int p,int x)
{
if(p==0)
{
return 0;
}
if(t[p].val==x)
{
return t[t[p].ls].siz+1;
}
if(x>t[p].val)
{
return t[t[p].ls].siz+t[p].cnt+query_rank(t[p].rs,x);
}
else
{
return query_rank(t[p].ls,x);
}
}
int query_kth(int p,int k)
{
if(p==0)
{
return 0;
}
if(k<=t[t[p].ls].siz)
{
return query_kth(t[p].ls,k);
}
k-=t[t[p].ls].siz;
if(k<=t[p].cnt)
{
return t[p].val;
}
k-=t[p].cnt;
return query_kth(t[p].rs,k);
}
查询前驱/后继
按照BST的性质找就好
int query_pre(int p,int x)
{
if(p==0)
{
return -inf;
}
if(t[p].val<x)
{
return max(t[p].val,query_pre(t[p].rs,x));
}
else
{
return query_pre(t[p].ls,x);
}
}
int query_back(int p,int x)
{
if(p==0)
{
return inf;
}
if(t[p].val>x)
{
return min(t[p].val,query_back(t[p].ls,x));
}
else
{
return query_back(t[p].rs,x);
}
}
下面是整个的代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
const int inf=1e7+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-')
{
f=-1;
}
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int root;
int n,tot;
struct treap
{
int ls;
int rs;
int cnt;
int siz;
int val;
int pri;
}t[maxn];
inline int rand()
{
static int seed=12345;
return seed=(int)seed*482711ll%2147483647;
}
void push_up(int p)
{
t[p].siz=t[t[p].ls].siz+t[t[p].rs].siz+t[p].cnt;
}
void right_rotate(int &p) //右旋
{
int x=t[p].ls;
t[p].ls=t[x].rs;
t[x].rs=p;
t[x].siz=t[p].siz;
push_up(p);
p=x;
}
void left_rotate(int &p) //左旋
{
int x=t[p].rs;
t[p].rs=t[x].ls;
t[x].ls=p;
t[x].siz=t[p].siz;
push_up(p);
p=x;
}
void insert(int &p,int x) //插入
{
if(p==0)
{
p=++tot;
t[p].siz=t[p].cnt=1;
t[p].val=x;
t[p].pri=rand();
return ;
}
t[p].siz++;
if(t[p].val==x)
{
t[p].cnt++;
}
else
{
if(x>t[p].val)
{
insert(t[p].rs,x);
if(t[t[p].rs].pri<t[p].pri)
{
left_rotate(p);
}
}
else
{
insert(t[p].ls,x);
if(t[t[p].ls].pri<t[p].pri)
{
right_rotate(p);
}
}
}
}
void remove(int &p,int x) //删除
{
if(p==0)
{
return ;
}
if(t[p].val==x)
{
if(t[p].cnt>1)
{
t[p].cnt--;
t[p].siz--;
}
else
{
if(t[p].ls==0 || t[p].rs==0)
{
p=t[p].ls+t[p].rs;
}
else
{
if(t[t[p].ls].pri<t[t[p].rs].pri)
{
right_rotate(p);
remove(p,x);
}
else
{
left_rotate(p);
remove(p,x);
}
}
}
}
else
{
if(x>t[p].val)
{
t[p].siz--;
remove(t[p].rs,x);
}
else
{
t[p].siz--;
remove(t[p].ls,x);
}
}
}
int query_rank(int p,int x)
{
if(p==0)
{
return 0;
}
if(t[p].val==x)
{
return t[t[p].ls].siz+1;
}
if(x>t[p].val)
{
return t[t[p].ls].siz+t[p].cnt+query_rank(t[p].rs,x);
}
else
{
return query_rank(t[p].ls,x);
}
}
int query_kth(int p,int k)
{
if(p==0)
{
return 0;
}
if(k<=t[t[p].ls].siz)
{
return query_kth(t[p].ls,k);
}
k-=t[t[p].ls].siz;
if(k<=t[p].cnt)
{
return t[p].val;
}
k-=t[p].cnt;
return query_kth(t[p].rs,k);
}
int query_pre(int p,int x)
{
if(p==0)
{
return -inf;
}
if(t[p].val<x)
{
return max(t[p].val,query_pre(t[p].rs,x));
}
else
{
return query_pre(t[p].ls,x);
}
}
int query_back(int p,int x)
{
if(p==0)
{
return inf;
}
if(t[p].val>x)
{
return min(t[p].val,query_back(t[p].ls,x));
}
else
{
return query_back(t[p].rs,x);
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
int opt=read();
int x=read();
if(opt==1)
{
insert(root,x);
}
if(opt==2)
{
remove(root,x);
}
if(opt==3)
{
cout<<query_rank(root,x)<<endl;
}
if(opt==4)
{
cout<<query_kth(root,x)<<endl;
}
if(opt==5)
{
cout<<query_pre(root,x)<<endl;
}
if(opt==6)
{
cout<<query_back(root,x)<<endl;
}
}
return 0;
}
扩展:Fhq_treap 和 Splay
Fhq_treap是一种NB的treap(无旋treap)
Splay又名伸展树,也是一种平衡树
以上二者最大的共性就是我都不会所以先去学习啦!
其实,平衡树的种类还有很多,感兴趣的话可以自行查找一下
自分の命よりも大切なものは、こんなに探しやすいものではありません