平衡树是一类动态的数据结构。这是它最大的特点,它可以维护修改操作,删除操作,前驱后继操作,查找数k的排名,查找第k大等功能。
Splay
Spaly,意为伸展,所以Splay又有伸展树的别称。其核心思想就是伸展,用一句话概括就是在每次操作时将结点旋转到根,听起来复杂度很高。但是均摊复杂度是logn(树高)的。基于这种中心思想,就有了Splay这种数据结构。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
#include<stack>
#define root sp[0].ch[1]
using namespace std;
const int INF=2147480000;
struct node{
int v,fa,ch[2],rp,sum;
}sp[100010];
void update(int x)
{
sp[x].sum=sp[sp[x].ch[0]].sum+sp[sp[x].ch[1]].sum+sp[x].rp;
}
bool identify(int x)
{
return x==sp[sp[x].fa].ch[1];
}
void connect(int x,int f,int son)
{
sp[x].fa=f;
sp[f].ch[son]=x;
}
void rotate(int x)
{
int f=sp[x].fa;
int gf=sp[f].fa;
int son=identify(x);
int fson=identify(f);
int B=sp[x].ch[son^1];
connect(B,f,son);
connect(f,x,son^1);
connect(x,gf,fson);
update(f);
update(x);
}
void splay(int x,int to)
{
to=sp[to].fa;
while(sp[x].fa!=to)
{
int up=sp[x].fa;
if(sp[up].fa==to)
{
rotate(x);
}
else if(identify(x)==identify(up))
{
rotate(up),rotate(x);
}
else
{
rotate(x),rotate(x);
}
}
}
int tot;
void create(int x,int f,int son)
{
sp[++tot].v=x;
sp[tot].sum=sp[tot].rp=1;
connect(tot,f,son);
}
void destroy(int x)
{
sp[x].fa=sp[x].ch[0]=sp[x].ch[1]=sp[x].sum=sp[x].rp=sp[x].v=0;
}
int find(int now,int v)
{
if(sp[now].v==v)
{
splay(now,root);
return now;
}
int next=(v>sp[now].v);
find(sp[now].ch[next],v);
}
void push(int v)
{
if(tot==0)
{
root=1;
create(v,0,1);
}
else
{
int now=root;
while(1)
{
sp[now].sum++;
if(v==sp[now].v)
{
sp[now].rp++;
splay(now,root);
return;
}
int next=(v>sp[now].v);
if(!sp[now].ch[next])
{
create(v,now,next);
splay(tot,root);
return;
}
now=sp[now].ch[next];
}
}
}
void pop(int v)
{
int now=find(root,v);
if(!now)
return;
if(sp[now].rp>1)
{
sp[now].rp--;
sp[now].sum--;
}
else if(!sp[now].ch[0])
{
root=sp[now].ch[1];
sp[root].fa=0;
destroy(now);
}
else
{
int lef=sp[now].ch[0];
while(sp[lef].ch[1])
{
lef=sp[lef].ch[1];
}
splay(lef,sp[now].ch[0]);
int rig=sp[now].ch[1];
connect(rig,lef,1);
connect(lef,0,1);
update(lef);
destroy(now);
}
}
int rank(int v)
{
sp[0].sum=0;
int cur=0,now=root;
while(1)
{
if(sp[now].v==v)
{
int res=cur+sp[sp[now].ch[0]].sum+1;
splay(now,root);
return res;
}
if(now==0)
{
return 0;
}
if(v<sp[now].v)
{
now=sp[now].ch[0];
}
else
{
cur+=sp[sp[now].ch[0]].sum+sp[now].rp;
now=sp[now].ch[1];
}
}
}
int atrank(int x)
{
int now=root;
while(1)
{
int lefsum=sp[now].sum-sp[sp[now].ch[1]].sum;
if(x>sp[sp[now].ch[0]].sum && x<=lefsum)
{
break;
}
if(x<lefsum)
{
now=sp[now].ch[0];
}
else
{
x-=lefsum;
now=sp[now].ch[1];
}
}
splay(now,root);
return sp[now].v;
}
int lower(int x)
{
push(x);
int now=sp[root].ch[0];
while(sp[now].ch[1])
now=sp[now].ch[1];
int res=sp[now].v;
pop(x);
return res;
}
int upper(int x)
{
push(x);
int now=sp[root].ch[1];
while(sp[now].ch[0])
{
now=sp[now].ch[0];
}
int res=sp[now].v;
pop(x);
return res;
}
int main(void){
int op,x,y,z,t;
sp[0].v=-INF;
scanf("%d",&t);
while(t--)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d",&x);
push(x);
}
else if(op==2)
{
scanf("%d",&x);
pop(x);
}
else if(op==3)
{
scanf("%d",&x);
printf("%d\n",rank(x));
}
else if(op==4)
{
scanf("%d",&x);
printf("%d\n",atrank(x));
}
else if(op==5)
{
scanf("%d",&x);
printf("%d\n",lower(x));
}
else
{
scanf("%d",&x);
printf("%d\n",upper(x));
}
}
return 0;
}
另一种平衡树treap,我采用的写法是Zig-Zag式的写法。就是双旋treap。
#include<bits/stdc++.h>
#define ls(x) t[x].left_son
#define rs(x) t[x].right_son
#define v(x) t[x].value
#define p(x) t[x].priority
#define rep(x) t[x].repeat_time
#define s(x) t[x].size
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=200100;
struct TREE{
int left_son;
int right_son;
int value;
int priority;
int repeat_time;
int size;
}t[maxn];
int num,rt;
inline void update(const int &x){
s(x)=s(ls(x))+s(rs(x))+rep(x);
}
inline void right_rotate(int &k){
int y=ls(k);
ls(k)=rs(y);
rs(y)=k;
s(y)=s(k);
update(k);
k=y;
}
inline void left_rotate(int &k){
int y=rs(k);
rs(k)=ls(y);
ls(y)=k;
s(y)=s(k);
update(k);
k=y;
}
inline void insert(int &k ,const int &key){
if(!k){
k=++num;
ls(k)=rs(k)=0;
rep(k)=s(k)=1;
v(k)=key;
p(k)=rand();
return;
}
else
++s(k);
if(v(k)==key) ++rep(k);
else if(key<v(k)){
insert(ls(k),key);
if(p(ls(k))<p(k)) right_rotate(k);
}
else if(key>v(k)){
insert(rs(k),key);
if(p(rs(k))<p(k)) left_rotate(k);
}
return;
}
inline void del(int &k,const int &key){
if(v(k)==key){
if(rep(k)>1) --s(k),--rep(k);
else if( !ls(k) || !rs(k) ){
k=ls(k)+rs(k);
}
else if(p(ls(k))<p(rs(k))){
right_rotate(k);
del(k,key);
}
else{
left_rotate(k);
del(k,key);
}
return;
}
--s(k);
if(key<v(k)) del(ls(k),key);
else del(rs(k),key);
return;
}
inline int before(const int &key){
int x=rt,res=-inf;
while(x){
if(v(x)<key){
res=v(x);
x=rs(x);
}
else {
x=ls(x);
}
}
return res;
}
inline int after(const int &key){
int x=rt,res=-inf;
while(x){
if(v(x)>key){
res=v(x);
x=ls(x);
}
else {
x=rs(x);
}
}
return res;
}
inline int kth(int k){
int x=rt;
while(x){
if(s(ls(x))<k && s(ls(x))+rep(x)>=k){
return v(x);
}
if(s(ls(x))>=k) x=ls(x);
else{
k-=rep(x)+s(ls(x));
x=rs(x);
}
}
return 0;
}
inline int rank(const int &key){
int x=rt,res=0;
while(x){
if(v(x)==key) return res+s(ls(x))+1;
if(key<v(x)) x=ls(x);
else{
res+=rep(x)+s(ls(x));
x=rs(x);
}
}
return res;
}
int n;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int op,x;
scanf("%d%d",&op,&x);
if(op==1){
insert(rt,x);
}
if(op==2){
del(rt,x);
}
if(op==3){
printf("%d\n",rank(x));
}
if(op==4){
printf("%d\n",kth(x));
}
if(op==5){
printf("%d\n",before(x));
}
if(op==6){
printf("%d\n",after(x));
}
}
}
自我感觉本人treap学的比splay懂。
如果说splay的核心在于伸展操作的话,那么treap的核心也在于它的名字中。tree+heap,既要满足bst(二叉搜索树)的性质,又要满足堆的性质,这样每个点除了它自带的权值之外,还有一个随机出的优先级。这样可以做到均摊复杂度logn,而且随机数据退化成链的可能很低。
在每一次插入一个新数的时候,若它插入之后,不满足堆的性质(因为本身是按bst性质插入的,所以只用考虑堆的性质)。将插入的结点,他的兄弟节点,他的父节点看作一个组,那么若左节点是最小的(以小根堆为例),那么就要把左节点旋转到根上去,这是右旋操作。
概括一下可以分为下列几步
1.获取根节点A的左节点,即要旋转到根的结点B。
2.将A的父节点信息更新为原B的父节点信息,并修改A父相对应的信息。
3.将根结点A的左节点改为原B的右儿子,对应修改。
4.将B的右儿子改为A