FHQ 平衡树 (一)

FHQ 平衡树

fhq 平衡树由范浩强巨佬发明,基于treap,实现无旋保持平衡

treap=tree+heap(中文翻译:树堆

  • 其同时满足BST性质堆性质
    BST简单来说,就是一颗二叉树,根节点的左儿子节点所有值<根节点<根节点的右儿子的所有值

    堆的性质,即为二叉树中,父节点的值大于等于(或小于等于)左右儿子节点
    (在treap实现这个性质中,给定一个随机值用以维护堆性质)

这道题为例,讲解

操作

建点

struct node{
	int val; 
	int size;//子树大小 (包括自己 
	int dat;//给的随机值用来维护堆 
	int l,r;//左右儿子 
}t[maxn];
int nw(int val){
	t[++tot].val=val;
	t[tot].cnt=t[tot].size=1;
	t[tot].dat=rand();
	return tot;//返回节点编号
} 

更新子树大小

用于各种操作后的节点信息更新

void push_up(int p){
    t[p].size=t[t[p].l].size+t[t[p].r].size+1;//+1算上自身
} 

两个核心操作

split及merge

split(分裂)

(我裂开来)

分裂分两类,按值分裂按大小分裂

  • 按值分裂
    把树拆成两棵树,一棵都小于等于给定值,另一棵统统大于给定值

  • 按大小分裂
    把树拆成两棵树,一棵等于给定大小,剩余的放在另一棵树中

这道题是按照按值分离

维护区间信息,就按大小分裂,文艺平衡树(后面讲

为了方便起见,我们称分裂开的两个子树,一棵为左树,一棵为右树(左树的值全小于等于val,右树的值全大于val)

void split(int now,int val,int &x,int &y){//now为当前节点,val为给定值,&x为引用左树的某个节点,&y为引用右树的某个节点

	if(!now) x=y=0;//这个节点不存在,那他的儿子也都不存在 
	else{
		if(t[now].val<=val){ //给定值大于等于当前节点
			x=now;//则当前节点分到左树 ,准确来说是把这个节点的左子树分给左树,因为此时左子树中的所有值小于等于val
			split(t[now].r,val,t[now].r,y);//考虑右子树中有多少能分裂给左树
			//根据BST性质,右子树的左子树仍有可能有小于于给定值
		}
		else{//反之 
			y=now;//则当前节点分到右树 准确来说是把这个节点的右子树分给右树,因为此时右子树中的所有值大于val
			split(t[now].l,val,x,t[now].l)
			//左子树的右子树,有大于给定值的可能 
		} 
	} push_up(now);//更新节点值
}

(分裂并不影响treap的性质)

merge(合并)

合并把分开的两个树合并,左树上的所有值小于等于右树上的所有值(当然合并后的树仍然满足treap性质)

int merge(int x,int y){//返回合并后树的根的编号 //要严格保证左树中的所有值<右树中的所有值即x<y
	if(!x || !y) return x+y; //如果没有某个儿子就返回另一个儿子
	if(t[x].dat>t[y].dat){//我们按照大根堆处理(小根堆也可 
		//根据大根堆,x一定是y的父亲
		//根据bst性质,x的所有值都是小于y的所有值的
		//所以y是x的右儿子,将x的右儿子和y合并 
		t[x].r=merge(t[x].r,y);
		update(x);return x;//更新节点 ,返回根 
	} 
	else{//与上面相反,可推出 
		t[y].l=merge(x,t[y].l); 
		update(y);return y;
	} 
} 

插入

插入某个值,只需要按照这个值将其分裂,再把这个左树,值
右数合并即可

左树分裂出的所有值一定小于等于val,右树一定大于等于val,y树上的所有值一定都大于等于val,val这个节点就可以当作树处理就可以与左树合并再与右树合并

void insert(int val){
	int x,y;
	split(root,val,x,y);//先我裂开 
	root=merge(merge(x,nw(val)),y);//我又合好了 
}

删除

受到插入的启发(其实就是插入的逆操作),我们先按val把树分离成左树,右树
再把左树按照val-1分离成左树左左树右
此时左树左的值全都小于val,左树右的的值都等于val
然后把左树右的踢出去(根:我爬我爬
然后再把剩下的都合并就可以

void del(int val){
	split(root,val,x,z);
	split(x,val-1,x,y);
	y=merge(t[y].l,t[y].r);//踢出根(不要爹),就把他的左右儿子合并 
	root=merge(merge(x,y),z); 
} 

查询值的排名

排名=小于val的值的个数+1

只需要按照val-1,分裂,左树的值都小于val,那么左树的大小+1,即为排名

void getrank(int val){
	split(root,val-1,x,y);
	printf("%d\n",t[x].size+1);
	root=merge(x,y);//裂了还要和好 
}

查询排名的值

void getval(int rank){
	int now=root;
	while(now){
		if(t[t[now].l].size+1==rank) break;//找到排名,跳出去 
		else if(t[t[now].l].size>=rank) now=t[now].l;//左子树的大小等于rank,则说明val一定在左子树内(BST性质) 
		else{//在右子树内 
			rank-=t[t[now].l].size+1;//那么在右子树的排名=rank-左子树的大小+1(+1是因为算上了根节点) 
			now=t[now].r;
		}
	} printf("%d\n",t[now].val);
}

前驱

前驱:按val-1分裂成左树和右树

左树最右的数即为val的前驱

void  getpre(int val){
	split(root,val-1,x,y);//x为左树,y为右数 
	int now=x;
	while(t[now].r) now=t[now].r;
	printf("%d",t[now].val);
	root=merge(x,y);//裂开记得拼回去 
} 

后继

后继:按val分裂成左树和右树

右树最左的数即为val的后继

void getnex(int val){
	split(root,val,x,y);
	int now=y;
	while(t[now].l) now=t[now].l;
	printf("%d",t[now].val);
	root=merge(x,y)//同理 
}

完整代码

#include<iostream>
#include<ctime>
#include<cstdio>
#include<cstdlib>
using namespace std;

int n,root,tot=0;
const int maxn=1e5+10;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}return x*f;
} 
struct node{
	int dat,size,val,l,r;
}t[maxn]; 
int nw(int val){
	t[++tot].val=val;
	t[tot].size=1;
	t[tot].dat=rand();
	return tot;
}
void push_up(int p){
	t[p].size=1+t[t[p].l].size+t[t[p].r].size;
} 
void split(int now,int val,int &x,int &y){
	if(!now){
		x=y=0;return ;
	}	
	else{
		if(t[now].val<=val){
			x=now;
			split(t[now].r,val,t[now].r,y);
		}
		else {
			y=now;
			split(t[now].l,val,x,t[now].l);
		} push_up(now);
	}
}
int merge(int x,int y){
	if(!x||!y) return x+y;
	if(t[x].dat>t[y].dat){
		t[x].r=merge(t[x].r,y);
		push_up(x);return x;
	}else{
		t[y].l=merge(x,t[y].l);
		push_up(y);return y;
	}
}
int x,y,z;
void inser(int val){
	split(root,val,x,y);
	root=merge(merge(x,nw(val)),y);
}
void del(int val){
	split(root,val,x,z);
	split(x,val-1,x,y);
	y=merge(t[y].l,t[y].r);
	root=merge(merge(x,y),z);
}
void getrank(int val){
	split(root,val-1,x,y);
	printf("%d\n",t[x].size+1);
	root=merge(x,y);
}
void getval(int rank){
	int now=root;
	while(now){
		if(t[t[now].l].size+1==rank) break;
		if(t[t[now].l].size>=rank) now=t[now].l;
		else{
			rank-=t[t[now].l].size+1;
			now=t[now].r;
		}
	}printf("%d\n",t[now].val);
} 
void getpre(int val){
	split(root,val-1,x,y);
	int now=x;
	while(t[now].r) now=t[now].r;
	printf("%d\n",t[now].val);
	root=merge(x,y);
}
void getnex(int val){
	split(root,val,x,y);
	int now=y;
	while(t[now].l) now=t[now].l;
	printf("%d\n",t[now].val);
	root=merge(x,y);
}
int main(){
	n=read();
	while(n--){
		int op=read(),val=read();
		switch(op){
			case 1:inser(val);break; 
			case 2:del(val);break; 
			case 3:getrank(val);break; 
			case 4:getval(val);break; 
			case 5:getpre(val);break; 
			case 6:getnex(val);break; 
		} 
	}return 0;
}

posted @ 2021-08-19 16:42  归游  阅读(328)  评论(0编辑  收藏  举报