平衡树

平衡树

平衡:左右子树高度差的绝对值<=1称为平衡
用途:
1、插入一个数x
2、删除一个数x
3、查询一个数x(其排名,其前驱后继)
4、查询排名为k的数x
5、快速合并与分裂
6、维护区间修改、查询、翻转
7、维护其它信息
0)AVL:最普通的平衡树,维护[高度],只要出现了不平衡的条件,就通过左右单旋、双旋平衡
实际效果一般,但高度平衡

1)Treap:

一句话:符合堆与二叉搜索树性质,但弱平衡,实现较为简单
heap 堆:(小根堆) 父节点val值大于子节点
二叉搜索树: 左子树val全小于根节点,右子树val全大于根节点
两者实际上是矛盾的,但是可以用一个priority值维护堆得性质,用val维护二叉搜索树的性质
解决的问题:在数据不随机的情况下,二叉搜索树会退化成一条链,那么查询复杂度退化为O(n)
           那么不妨利用堆随机化priority的属性,打乱节点插入顺序,使搜索树的复杂度期望值保持在log2n

1.有旋Treap

类似AVL分左旋右旋 在满足二叉搜索树的条件下根据堆的优先级对 treap 进行平衡操作。
有旋 treap 在做普通平衡树题的时候,是所有平衡树中常数较小的
用 treap 对每个节点定义一个由 rand 得到的权值,从而防止特殊数据卡
同时在每次删除/插入时通过这个权值决定要不要旋转即可,其他操作与二叉搜索树类似。

 旋转:
  左旋,就是把右子树变成根节点;右旋反之
  旋转过后,跟旋转方向相同的子节点变成了原来的根节点

 插入:
  跟普通搜索树插入的过程没啥区别,但是需要在插的过程中 [通过旋转来维护树堆中堆的性质]

 删除:
  分类讨论,不同的情况有不同的处理方法,删完了树的大小会有变化,要注意更新
  如果要删的节点有左子树和右子树,就要考虑删除之后让谁来当父节点(维护 priority 小的节点在上面)。

 根据值查询排名:
  查询以 cur 为根节点的子树中,val 值的大小的排名

 根据排名查询值:
  a.左子树 排名一定小于等于左子树的大小
  b.根节点/当前节点 排名应该 >= 左子树的大小,并且<= 左子树的大小 + 根节点的重复次数
  c.右子树 不然的话就在右子树
           	1 -> |左子树的节点|根节点|右子树的节点| -> n
                       			^
                       			要查的排名
                 			转换成基于右子树的排名
	 1 -> |右子树的节点| -> n
  		 	^
   			要查的排名
      转换方法就是直接把排名减去左子树的大小和根节点的重复数量

  查询第一个比 val 小的节点 
   val 比当前节点值大的时候才会被更改的,返回这个变量就是返回 val 最后一次比当前节点的值大

  查询第一个比 val 大的节点 同理,符号换了一下而已

My_code:

#include <bits/stdc++.h>
using namespace std;

const int N=1e6+10;

int read() {

	int x=0,f=1;
	char ch=getchar();
	while(ch<48||ch>57) {
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57) {
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;

}

struct treap {

	int l[N],r[N],val[N],pri[N],siz[N],w[N];
	int sz,ans,rt;

	inline void push_up(int x) {
		siz[x]=siz[l[x]]+siz[r[x]]+w[x];
	}

	void lrotate(int &k) {

		int t=r[k];
		r[k]=l[t];
		l[t]=k;
		siz[t]=siz[k];
		push_up(k);
		k=t;

	}

	void rrotate(int &k) {

		int t=l[k];
		l[k]=r[t];
		r[t]=k;
		siz[t]=siz[k];
		push_up(k);
		k=t;

	}

	void insertx(int &k,int x) {

		if(!k) {
			sz++;
			k=sz;
			siz[k]=1;
			w[k]=1;
			val[k]=x;
			pri[k]=rand();
			return ;
		}
		siz[k]++;
		if(val[k]==x) w[k]++;
		else if(val[k]<x) {
			insertx(r[k],x);
			if(pri[r[k]]<pri[k]) lrotate(k);
		} else {
			insertx(l[k],x);
			if(pri[l[k]]<pri[k]) rrotate(k);
		}

	}

	bool del(int &k,int x) {

		if(!k) return false;
		if(val[k]==x) {

			if(w[k]>1) {
				w[k]--;
				siz[k]--;
				return true;
			}
			if(l[k]==0||r[k]==0) {
				k=l[k]+r[k];
				return true;
			} else if(pri[l[k]]<pri[r[k]]) {
				rrotate(k);
				return del(k,x);
			} else {
				lrotate(k);
				return del(k,x);
			}

		} else if(val[k]<x) {
			bool can=del(r[k],x);
			if(can) siz[k]--;
			return can;
		} else {
			bool can=del(l[k],x);
			if(can) siz[k]--;
			return can;
		}

	}

	int queryrank(int k,int x) {

		if(!k) return 0;
		if(val[k]==x) return siz[l[k]]+1;
		else if(val[k]<x) return siz[l[k]]+w[k]+queryrank(r[k],x);
		else return queryrank(l[k],x);

	}

	int querynum(int k,int x) {

		if(!k) return 0;
		if(x<=siz[l[k]]) return querynum(l[k],x);
		else if(siz[l[k]]+w[k]<x) return querynum(r[k],x-siz[l[k]]-w[k]);
		else return val[k];

	}

	void querypre(int k,int x) {

		if(!k) return ;
		if(val[k]<x) ans=k,querypre(r[k],x);
		else querypre(l[k],x);

	}

	void querynxt(int k,int x) {

		if(!k) return ;
		if(val[k]>x) ans=k,querynxt(l[k],x);
		else return querynxt(r[k],x);

	}

} Tree;
int n;

int main() {

	srand(time(NULL));
	n=read();
	while(n--) {

		int op=read(),x=read();
		if(op==1) Tree.insertx(Tree.rt,x);
		if(op==2) Tree.del(Tree.rt,x);
		if(op==3) {

			int p=Tree.queryrank(Tree.rt,x);
			printf("%d\n",p);

		}
		if(op==4) {

			int p=Tree.querynum(Tree.rt,x);
			printf("%d\n",p);
		}
		if(op==5) {

			Tree.ans=0;
			Tree.querypre(Tree.rt,x);
			printf("%d\n",Tree.val[Tree.ans]);
			
		}
		if(op==6) {

			Tree.ans=0;
			Tree.querynxt(Tree.rt,x);
			printf("%d\n",Tree.val[Tree.ans]);

		}

	}

	return 0;
}

2.无旋Treap(FHQ Treap)

 ...
      

2)Splay

旋转谁就是说将哪个结点提升到其父结点的位置
单旋:1.左旋:右儿子向左旋 2.右旋:左儿子向右旋
双旋:根据当前结点 x,父结点 f 和祖父结点 g 的形态(如果没有祖父结点,那么做一次单旋就行了)来决定具体的旋转方案
异构调整:x,f(father),g(grand) 不在一条线上。此时操作和单旋一样,只需要将当前结点旋转两次即可
同构调整:x,f(father),g(grand) 在一条线上。此时我们需要先旋转 f,再旋转 x
双旋虽然不能把树变得非常平衡,但是均摊复杂度是正确的

splay:这个操作能够把一个结点 x 旋转到 top 结点的子结点的位置
(如果给定 top=0 则认为是旋转到根结点)
让 Splay 保持大致平衡

删除操作:首先,我们把当前结点 Splay 到根结点。
如果相同值的节点不止一个,那很简单,只要将值的数量减一就行了;
如果当前结点没有右儿子,也就是没有后继,那只需要将根节点的指针指到左儿子上就行了;
否则,我们找到当前结点的后继并将其 Splay 到当前结点的儿子结点处,此时这个后继一定没有左儿子。
于是我们直接将后继设为根结点,并 connect 后继和当前节点的左儿子就行了。

My_code:

//学习了番茄的splay
#include <bits/stdc++.h>
#define ls t[x].l 
#define rs t[x].r 
using namespace std;

const int inf=1e9;
const int N=2e5+10;

int read(){
	
	int x=0,f=1;
	char ch=getchar();
	while(ch<48||ch>57){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>=48&&ch<=57){
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;
	
}

int n,op,q,rt,tot=0;

struct Splay{
	
	int fa,val,cnt,siz,l,r;
	
}t[N];

int newnode(int x,int y){
	t[++tot].fa=y;
	t[tot].val=x;
	t[tot].siz=t[tot].cnt=1;
	return tot;
}

void to_clear(int x) {
	
	t[x].fa=t[x].val=t[x].siz=t[x].cnt=ls=rs=0;
	
}

void push_up(int x){
	
	to_clear(0);
	t[x].siz=t[ls].siz+t[rs].siz+t[x].cnt;
	
}

void rotate(int x){
	
	int f1=t[x].fa,f2=t[f1].fa;
	if(f2){
		if(t[f2].l==f1) t[f2].l=x;
		else t[f2].r=x;
	}
	t[x].fa=f2;
	t[f1].fa=x;
	if(t[f1].l==x) t[f1].l=rs,t[rs].fa=f1,rs=f1;
	else t[f1].r=ls,t[ls].fa=f1,ls=f1;
	push_up(f1);
	push_up(x);
	
}

void splay(int x){
	
	while(t[x].fa){
		
		int f1=t[x].fa,f2=t[f1].fa;
		if(f2){
			if((t[f1].l==x)^(t[f2].l==f1)) rotate(x);
			else rotate(f1);
		}
		rotate(x);
		
	}
	rt=x;
	
}

void insert(int &x,int y,int fa){
	
	if(!x) {
		x=newnode(y,fa);
		splay(x);
		return ;
	}
	if(t[x].val==y) {
		t[x].cnt++;
		t[x].siz++;
		return ;
	}
	if(t[x].val<y) insert(rs,y,x);
	else insert(ls,y,x);
	push_up(x);
	
}

int get_rank(int x,int y){
	
	if(!x) return 0;
	int rk=t[ls].siz;
	if(t[x].val==y) {
		splay(x);
		return rk+1;
	}
	if(t[x].val<y) return get_rank(rs,y)+rk+t[x].cnt;
	else return get_rank(ls,y);
	
}

void del(int x){
	
	int rk=get_rank(rt,x);
	rk=rt;
	t[rk].cnt--;
	if(t[rk].cnt) return ;
	if(!t[rk].l&&!t[rk].r) to_clear(rk),rk=rt=0;
	else if(!t[rk].l) rt=t[rk].r,t[rt].fa=0,to_clear(rk);
	else if(!t[rk].r) rt=t[rk].l,t[rt].fa=0,to_clear(rk);
	else {
		
		int p=t[rk].l;
		while(t[p].r) p=t[p].r;
		splay(p);
		t[p].r=t[rk].r;
		t[t[rk].r].fa=p;
		to_clear(rk);
		
	}
	push_up(rt);
	
}

int get_val(int x,int y){
	
	if(t[ls].siz<y&&t[ls].siz+t[x].cnt>=y) return t[x].val;
	if(t[ls].siz>=y) return get_val(ls,y);
	else return get_val(rs,y-t[ls].siz-t[x].cnt);
	
}

int get_pre(int x,int y){
	
	if(!x) return -inf;
	if(t[x].val<y) return max(t[x].val,get_pre(rs,y));
	else return get_pre(ls,y);
	
}

int get_nxt(int x,int y){
	
	if(!x) return inf;
	if(t[x].val>y) return min(t[x].val,get_nxt(ls,y));
	else return get_nxt(rs,y);
	
}

int main() {
	
	n=read();
	for(int i=1;i<=n;i++){
		
		op=read();
		q=read();
		if(op==1) insert(rt,q,0);
		if(op==2) del(q);
		if(op==3) printf("%d\n",get_rank(rt,q));
		if(op==4) printf("%d\n",get_val(rt,q));
		if(op==5) printf("%d\n",get_pre(rt,q));
		if(op==6) printf("%d\n",get_nxt(rt,q));
		
	}
	
	return 0;
}  
posted @   Diamondan  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示