BST,Treap学习随笔

BST,Treap学习随笔

0 前言

学习随笔

1 BST

二叉查找树

1.1 性质

对于一个点来说

左子树中所有权值均小于当前点权值小于右子树中所有点权值

还有一个神奇的性质:

中序遍历一下 发现天然有序

上图中序遍历即为1,2,3,4,5,6,7,8,9,10,11

1.2 支持基本操作

插入(\(logn\))(最坏\(n\)

删除(\(logn\))(最坏\(n\)

找v在树上大小排第几(\(logn\))(最坏\(n\)

求第k小(\(logn\))(最坏\(n\)

求前驱(权值严格小于v的最大值)(\(logn\))(最坏\(n\)

求后继(全职严格大于v的最小值)(\(logn\))(最坏\(n\)

(因为对于同一棵树满足BST性质的形状有很多种,在一些数据下树会变成一条链,从而使操作都退化到\(O(n)\)

1.3 维护

因为Treap为BST升级版,基本操作都一样,所以直接看Treap的维护吧

2 Treap

杂交数据结构

Treap=BST(二叉查找树)+Heap(堆)

2.1 性质

既满足BST(点权)

又满足Heap(我们给点附的优先值,这里优先值是rand出来的)(可以有效避免退化成一条链的情况)

优先值满足大根堆性质(小根堆也行 只要是个堆即可)

2.2 支持操作

插入(\(logn\)

删除(\(logn\)

找v在树上大小排第几(\(logn\)

求第k小(\(logn\)

求前驱(权值严格小于v的最大值)(\(logn\)

求后继(全职严格大于v的最小值)(\(logn\)

(因为有了rand出的优先级 树在没有被神秘宇宙射线影响的情况下是不会退化到链的)

2.3 维护

2.3.1 点的信息

struct node{
	int l,r,val,siz,cnt;//左节点,右节点,权值,子树大小(含自己),自己有几个
	unsigned pri;//随机生成的优先值
}a[N];

2.3.2 插入

2.3.2.1 zig,zag

发现严重问题:如何同时维护两个性质

我们左思右想 决定先满足其中一个性质

先满足BST性质

首先在原树上找到应该放当前权值的位置

然后需要在不破坏当前BST性质的情况下使rand出的优先值满足Heap

于是发明出zig(右旋),zag(左旋)操作

对应不同需交换情况

/*
     Y                X
    /        =>     /  \
   X         zig   A    Y 
 /  \                  /
A    B                B

 Y                    X
  \          =>     /  \
   X         zag   Y    B
 /   \              \
A    B               A

*/

发现原本的BST性质没有被破坏且X,Y父子关系还交换了

这就很像Heap中的挂在叶子节点一直往上交换的操作

size更新的时候需注意:我们在插入重复值时是直接把当前权值对应点中的cnt++

因此计算size时需要加上自己的cnt

void siz_update(int u){
	if(!u)return;
	a[u].siz=a[a[u].l].siz+a[a[u].r].siz+a[u].cnt;//加上自己的个数
}

void zig(int &y){//右旋  将y与y的左子树交换
    //& 是为了方便直接将当前访问的y的值改变为x 
	int x=a[y].l;//y的左子树
	a[y].l=a[x].r;//将x的右子树挂在y的左子树上
	a[x].r=y;//将y挂在x的左子树上
	siz_update(y);//先更新y的size(因为y现在在x下面)
	siz_update(x);//再更新x的size
	y=x;
}

void zag(int &y){//左旋 将y与y的右子树交换
	int x=a[y].r;//y的右子树
	a[y].r=a[x].l;//将x的左子树挂在y的右子树上
	a[x].l=y;//将y挂在x的左子树上
	siz_update(y);//更新size
	siz_update(x);
	y=x;
}

2.3.2.2 Insert

有了zig,zag我们就可以愉快地insert了

int add(int v){
	a[++tot]={0,0,v,1,1,rnd()};
    //l,r初始化为0 表示没有左节点和右节点 
    //val=v
    //siz和cnt初始化为1
    //pri优先级rand一下
	return tot;
}
void Insert(int &rt,int v){//& 也是方便直接改值
	if(!rt){//扫到叶子节点了
		rt=add(v);
		siz_update(rt);//别忘了更新大小
		return;
	}
	if(v==a[rt].val){//有这个权值了
		a[rt].cnt++;//直接加
		siz_update(rt);//依旧别忘了更新
		return;
	}else if(v<a[rt].val){//比当前权值小 说明还要往左走
		Insert(a[rt].l,v);//以左节点为根继续维护
		if(a[a[rt].l].pri<a[rt].pri){//维护Heap的性质 这里只可能是左节点有问题
			zig(rt);//右旋交换
		}
	}else if(v>a[rt].val){//同上
		Insert(a[rt].r,v);
		if(a[a[rt].r].pri<a[rt].pri){
			zag(rt);
		}
	}
	siz_update(rt);//别忘了更新
}

2.3.3 删除

void Erase(int &rt,int v){
	if(!rt){//找到了 返回
		return;
	}
	if(v==a[rt].val){//找到了
		if(a[rt].cnt>1){//还有 直接减
			a[rt].cnt--;
			siz_update(rt);//别忘了更新!
			return;
		}else if(!a[rt].l&&!a[rt].r){//交换到了叶子节点就可以直接删除了
			rt=0;//直接一直返回
			return;
		}else if(!a[rt].r||(a[rt].l&&a[a[rt].l].pri>a[a[rt].r].pri)){
            //没有右子树 或者有左子树并且左子树优先级比右子树优先级大
			zig(rt);//右旋上来
			Erase(a[rt].r,v);//交换过后原本要删的点在右边
		}else if(!a[rt].l||(a[rt].r&&a[a[rt].r].pri>a[a[rt].l].pri)){
            //没有左子树 或者有右子树并且右子树优先级比左子树优先级大
			zag(rt);//左旋上来
			Erase(a[rt].l,v);//交换后原本要删的点在左边
		}
	}else if(v<a[rt].val){//找左子树
		Erase(a[rt].l,v);
	}else if(v>a[rt].val){//找右子树
		Erase(a[rt].r,v);
	}
	siz_update(rt);//更新!!!!!!
}

2.3.4 查找排名

int Rank(int rt,int v){
	if(!rt)return 1;//空树则自己就是第一
	if(v==a[rt].val){//找到了
		return a[a[rt].l].siz+1;//左子树中所有一定比自己小 加一就是排名
	}else if(v<a[rt].val){
		return Rank(a[rt].l,v);//一定在左子树中排名
	}else{
		return a[a[rt].l].siz+a[rt].cnt+Rank(a[rt].r,v);
        //左子树大小加中间点个数再加右子树中排名
	}
}

2.3.5 找第k小

int kth(int rt,int k){
	if(!rt)return -1;//没找到
	if(k<=a[a[rt].l].siz){//如果还没左子树大小大 说明在左子树中
		return kth(a[rt].l,k);//递归左子树
	}
	k-=a[a[rt].l].siz;//如果比左子树大就减去
	if(k<=a[rt].cnt){//剩下的比中间点个数小 说明就是中间点
		return a[rt].val;
	}
	k-=a[rt].cnt;//还大 减去
	return kth(a[rt].r,k);//递归右子树
}

2.3.6 找前驱/后继

int ask_pre(int rt,int v){//前驱
	int res=-INF;//初始化为极小值
	while(rt){//一直找到底才是严格小于他的最大值
		if(v<=a[rt].val){//大了
			rt=a[rt].l;//往左边找小的
		}else{
			res=a[rt].val;//够了
			rt=a[rt].r;//往右边找有没有正在比v小的情况下更大的值
		}
	}
	return res;
}

int ask_nex(int rt,int v){//后继  细节同上
	int res=INF;
	while(rt){
		if(v>=a[rt].val){
			rt=a[rt].r;
		}else{
			res=a[rt].val;
			rt=a[rt].l;
		}
	}
	return res;
}

2.4 模板题

【模板】普通平衡树

附上代码

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

const int N=2e5+5;
const int INF=0x3f3f3f3f3f3f3f;

static unsigned seed=(unsigned)chrono::steady_clock::now().time_since_epoch().count();
unsigned rnd(){
	seed^=seed<<12,seed^=seed>>5,seed^=seed<<11;
	return seed;
}
struct node{
	int l,r,val,siz,cnt;
	unsigned pri;
}a[N];
int tot=0;
int root=0;
int n;

int add(int v){
	a[++tot]={0,0,v,1,1,rnd()};
	return tot;
}

void siz_update(int u){
	if(!u)return;
	

	a[u].siz=a[a[u].l].siz+a[a[u].r].siz+a[u].cnt;

}

void zig(int &y){
	int x=a[y].l;
	a[y].l=a[x].r;
	a[x].r=y;
	siz_update(y);
	siz_update(x);
	y=x;
}

void zag(int &y){
	int x=a[y].r;
	a[y].r=a[x].l;
	a[x].l=y;
	siz_update(y);
	siz_update(x);	
	y=x;
}

void Insert(int &rt,int v){
	if(!rt){
		rt=add(v);
		siz_update(rt);
		return;
	}
	if(v==a[rt].val){
		a[rt].cnt++;
		siz_update(rt);
		return;
	}else if(v<a[rt].val){
		Insert(a[rt].l,v);
		if(a[a[rt].l].pri<a[rt].pri){
			zig(rt);
		}
	}else if(v>a[rt].val){
		Insert(a[rt].r,v);
		if(a[a[rt].r].pri<a[rt].pri){
			zag(rt);
		}
	}
	siz_update(rt);
}

void Erase(int &rt,int v){
	if(!rt){
		return;
	}
	if(v==a[rt].val){
		if(a[rt].cnt>1){
			a[rt].cnt--;
			siz_update(rt);
			return;
		}else if(!a[rt].l&&!a[rt].r){
			rt=0;
			return;
		}else if(!a[rt].r||(a[rt].l&&a[a[rt].l].pri>a[a[rt].r].pri)){
			zig(rt);
			Erase(a[rt].r,v);
		}else if(!a[rt].l||(a[rt].r&&a[a[rt].r].pri>a[a[rt].l].pri)){
			zag(rt);
			Erase(a[rt].l,v);
		}
	}else if(v<a[rt].val){
		Erase(a[rt].l,v);
	}else if(v>a[rt].val){
		Erase(a[rt].r,v);
	}
	siz_update(rt);
}

int Rank(int rt,int v){
	if(!rt)return 1;
	if(v==a[rt].val){
		return a[a[rt].l].siz+1;
	}else if(v<a[rt].val){
		return Rank(a[rt].l,v);
	}else{
		return a[a[rt].l].siz+a[rt].cnt+Rank(a[rt].r,v);
	}
}

int kth(int rt,int k){
	if(!rt)return -1;
	if(k<=a[a[rt].l].siz){
		return kth(a[rt].l,k);
	}
	k-=a[a[rt].l].siz;
	if(k<=a[rt].cnt){
		return a[rt].val;
	}
	k-=a[rt].cnt;
	return kth(a[rt].r,k);
}

int ask_pre(int rt,int v){
	int res=-INF;
	while(rt){
		if(v<=a[rt].val){
			rt=a[rt].l;
		}else{
			res=a[rt].val;
			rt=a[rt].r;
		}
	}
	return res;
}

int ask_nex(int rt,int v){
	int res=INF;
	while(rt){
		if(v>=a[rt].val){
			rt=a[rt].r;
		}else{
			res=a[rt].val;
			rt=a[rt].l;
		}
	}
	return res;
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	while(n--){
		int op,x;
		cin>>op>>x;
		if(op==1){
			Insert(root,x);
		}else if(op==2){
			Erase(root,x);
		}else if(op==3){
			cout<<Rank(root,x)<<endl;
		}else if(op==4){
			cout<<kth(root,x)<<endl;
		}else if(op==5){
			cout<<ask_pre(root,x)<<endl;
		}else if(op==6){
			cout<<ask_nex(root,x)<<endl;
		}
	}
	return 0;
}


完结撒花🎉🎉🎉

posted @ 2026-01-17 11:03  xwy114514  阅读(8)  评论(0)    收藏  举报