平衡树学习(2)——Splay

Splay是一种平衡树,它的代码复杂度和时间复杂度稍弱于Treap,但由于其可以支持区间操作,所以在实战中还是有许多用处。
我们先来看看Splay的定义和基本思路。

伸展树(Splay Tree),也叫分裂树,它能在O(log n)内完成插入、查找和删除操作。在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
它的优势在于不需要记录用于平衡树的冗余信息。

Ps:我们介绍的Splay是双旋操作Splay。


数组定义:
struct node{
	int fa,siz,ct,val,son[2];
}T[MAXN];
//fa记录父亲节点,siz记录子树大小,val记录当前节点值,ct表示与当
//前节点值相同的点个数,son[2]记录其左儿子和右儿子。

预先操作:

1.clear:清空点k里的数据

void clear(int k){
	T[k].siz=T[k].ct=T[k].fa=T[k].val=T[k].son[0]=T[k].son[1]=0;
} 

2.up:统计k的子树大小

void up(int k){
	T[k].siz=T[L].siz+T[R].siz+T[k].ct;
} 

3.get:查询k是父亲的左儿子还是右儿子

int get(int k){
	return T[T[k].fa].son[1]==k;
}

旋转:

对于Splay来说,最重要的就是rotate旋转和splay伸展操作。
对于rotate操作,我们可以类比于Treap的旋转。

void rotate(int &k){
	int fa=T[k].fa,gran=T[fa].fa;
	int d1=get(k),d2=get(fa);
	T[T[k].son[d1^1]].fa=fa;
	T[fa].son[d1]=T[k].son[d1^1];T[k].son[d1^1]=fa;
	T[k].fa=gran;T[fa].fa=k;
	if(gran) T[gran].son[d2]=k;;
	up(fa);up(k);
}

splay操作是Rotate的升级版,该函数将子节点旋转到根,来保持Splay的复杂度。
每次的splay操作,我们都将其旋转到根节点。对于Splay的伸展操作,我们需要进行讨论:
1.当旋转的点中有根节点时,可以直接进行旋转。
这里写图片描述

2.当点x和其父亲,祖父三点共线,则先旋转x的父亲,然后旋转x。
这里写图片描述
3.如果x和其父亲,祖父三点不共线,旋转两次x。

void splay(int k){
	for(int fa;fa=T[k].fa;rotate(k))
	  if(T[fa].fa) rotate(get(fa)==get(k)?fa:k);
	root=k; 
}

删除:

删除操作需要进行讨论,如果对于值x存在多个,那直接将x所在节点k的size和ct减1即可。如果不是这样,我们继续讨论,如果k没有儿子,直接clear节点k即可。如果k只有一个儿子,就将它的儿子接上来即可。如果k有两个儿子,就将它的前驱接到它父亲上,然后将k的右儿子接到前驱上,这样就删去了k节点。

int find(int k,int val){   //先求出要删除点的位置,并进行splay伸展
	if(!k) return 0;
	if(val==T[k].val){
		splay(k);return k;
	}
	return find(T[k].son[val>T[k].val],val);
}
void delet(int x){
	int pl=find(root,x);
	if(!pl) return;
	if(T[root].ct>1){
		T[root].ct--,T[root].siz--;return;
	}
	if(!T[root].son[1]&&!T[root].son[0]){
		clear(root),root=0;return;
	}
	if(!T[root].son[1]||!T[root].son[0]){
		int rt=root;
		root=T[root].son[0]+T[root].son[1];
		T[root].fa=0;clear(rt);return;
	}
    int rt=root;
    pre(root,x);splay(dist);
    T[T[rt].son[1]].fa=root;T[root].son[1]=T[rt].son[1];
    clear(rt);up(root);T[root].fa=0;
    return;
}

其它操作:

如求取前驱,后继等操作,类似于Treap

void insert(int &k,int val,int pos){   //插入操作
	if(!k){
		k=++sz;
		T[k].ct=T[k].siz=1;T[k].fa=pos;T[k].son[0]=T[k].son[1]=0;T[k].val=val;
		dist=k;return; 
	}
	if(val==T[k].val){
		dist=k;T[k].siz++;T[k].ct++;return;
	}
	if(val<T[k].val) insert(T[k].son[0],val,k);
	if(val>T[k].val) insert(T[k].son[1],val,k);
	up(k);
}
int searchRANK(int k,int x){  //查询x排在第几
	if(!k) return 0;
	if(x<T[k].val) return searchRANK(L,x);
	if(x==T[k].val) return T[L].siz+1; 
	if(x>T[k].val) return T[L].siz+T[k].ct+searchRANK(R,x);
}
int searchPLACE(int k,int x){    //查询排在x的数是几
	if(!k) return 0;
	if(x<=T[L].siz) return searchPLACE(L,x);
	x-=T[L].siz;
	if(x<=T[k].ct) return T[k].val;
	x-=T[k].ct;
	return searchPLACE(R,x);
}
void pre(int k,int val){   //前驱,dist记录位置
	if(!k) return;
	if(val<=T[k].val) pre(L,val);
	else dist=k,pre(R,val);
}
void ahe(int k,int val){   //后继
	if(!k) return;
	if(val>=T[k].val) ahe(R,val);
	else dist=k,ahe(L,val);
}

合并:

①当S1中的所有元素小于S2(比如S1和S2是刚刚分裂出来的)时,只需要把S1最大的点伸展到根,然后连边即可。
②当S1和S2大小任意时,启发式合并,把小的往大的身上挪。
分裂:(以k为界限,左边小于或等于k,右边大于或等于k)


BZOJ3224模板:
#include<bits/stdc++.h>
#define L T[k].son[0]
#define R T[k].son[1]
#define MAXN 100005
using namespace std;
int read(){
	char c;int x=0,y=1;while(c=getchar(),(c<'0'||c>'9')&&c!='-');if(c=='-') y=-1;else x=c-'0';
	while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x*y;
}
int root,dist,sz,n;
struct node{
	int fa,siz,ct,val,son[2];
}T[MAXN];
void clear(int k){
	T[k].siz=T[k].ct=T[k].fa=T[k].val=T[k].son[0]=T[k].son[1]=0;
} 
void up(int k){
	T[k].siz=T[L].siz+T[R].siz+T[k].ct;
} 
int get(int k){
	return T[T[k].fa].son[1]==k;
}
void rotate(int &k){
	int fa=T[k].fa,gran=T[fa].fa;
	int d1=get(k),d2=get(fa);
	T[T[k].son[d1^1]].fa=fa;
	T[fa].son[d1]=T[k].son[d1^1];T[k].son[d1^1]=fa;
	T[k].fa=gran;T[fa].fa=k;
	if(gran) T[gran].son[d2]=k;;
	up(fa);up(k);
}
void splay(int k){
	for(int fa;fa=T[k].fa;rotate(k))
	  if(T[fa].fa) rotate(get(fa)==get(k)?fa:k);
	root=k; 
}
void insert(int &k,int val,int pos){
	if(!k){
		k=++sz;
		T[k].ct=T[k].siz=1;T[k].fa=pos;T[k].son[0]=T[k].son[1]=0;T[k].val=val;
		dist=k;return; 
	}
	if(val==T[k].val){
		dist=k;T[k].siz++;T[k].ct++;return;
	}
	if(val<T[k].val) insert(T[k].son[0],val,k);
	if(val>T[k].val) insert(T[k].son[1],val,k);
	up(k);
}
int searchRANK(int k,int x){
	if(!k) return 0;
	if(x<T[k].val) return searchRANK(L,x);
	if(x==T[k].val) return T[L].siz+1; 
	if(x>T[k].val) return T[L].siz+T[k].ct+searchRANK(R,x);
}
int searchPLACE(int k,int x){
	if(!k) return 0;
	if(x<=T[L].siz) return searchPLACE(L,x);
	x-=T[L].siz;
	if(x<=T[k].ct) return T[k].val;
	x-=T[k].ct;
	return searchPLACE(R,x);
}
void pre(int k,int val){
	if(!k) return;
	if(val<=T[k].val) pre(L,val);
	else dist=k,pre(R,val);
}
void ahe(int k,int val){
	if(!k) return;
	if(val>=T[k].val) ahe(R,val);
	else dist=k,ahe(L,val);
}
int find(int k,int val){
	if(!k) return 0;
	if(val==T[k].val){
		splay(k);return k;
	}
	return find(T[k].son[val>T[k].val],val);
}
void delet(int x){
	int pl=find(root,x);
	if(!pl) return;
	if(T[root].ct>1){
		T[root].ct--,T[root].siz--;return;
	}
	if(!T[root].son[1]&&!T[root].son[0]){
		clear(root),root=0;return;
	}
	if(!T[root].son[1]||!T[root].son[0]){
		int rt=root;
		root=T[root].son[0]+T[root].son[1];
		T[root].fa=0;clear(rt);return;
	}
    int rt=root;
    pre(root,x);splay(dist);
    T[T[rt].son[1]].fa=root;T[root].son[1]=T[rt].son[1];
    clear(rt);up(root);T[root].fa=0;
    return;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++){
		int x=read(),y=read();
		if(x==1) insert(root,y,0),splay(dist);
		if(x==2) delet(y);
		if(x==3) printf("%d\n",searchRANK(root,y));
		if(x==4) printf("%d\n",searchPLACE(root,y));
		if(x==5) dist=0,pre(root,y),printf("%d\n",T[dist].val);
		if(x==6) dist=0,ahe(root,y),printf("%d\n",T[dist].val); 
	}
	return 0;
}

文献资料:图片转自博客ZigZagK,感谢ZZK大佬

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值