Treap 学习笔记


前置知识:二叉搜索树(BST)
众所周知,二叉搜索树的会被数据卡,所以我们要对它进行一定的操作,让它平衡一点。
Treap是通过旋转+随机化的方法来进行该操作的,所以不会被卡,但是功能少,只能做最基础的操作,不能区间反转。

算法实现

众所周知,Treap=Tree(树)+Heap(堆)

struct JTZ{
	int val,siz,cnt,ls,rs,f;
}a[maxn];

我们在每个节点中相对于二叉搜索树中加一个信息:\(f\) ,这是一个随机数。
我们需要保证以下内容:
首先保证它具有二叉搜索树的性质(这不是废话)
其次,我们的 \(f\) 变量必须满足堆的性质,就是父亲节点的 \(f\) 比它两个儿子的 \(f\) 都小。
如何维护它堆的性质?旋转即可。

算法核心

算法核心就是旋转,大致长这个样子:

Update 2021.3.9:图片有误,现已更新
其实就是两个操作:Zig和Zag
由于Zig和Zag都不会影响二叉搜索树的性质,所以无论怎么转答案都是对的,但是效率可能会改变

void zag(int &p){//right
	int k=a[p].ls;
	a[p].ls=a[k].rs; a[k].rs=p;
	p=k; up(a[p].rs); up(p);
	return;
}
void zig(int &p){//left
	int k=a[p].rs;
	a[p].rs=a[k].ls; a[k].ls=p;
	p=k; up(a[p].ls); up(p);
	return;
}

接下来是删除操作,只要把该节点旋转到叶子节点就可以了。
记得在插入时维护序列。

代码

#include<ctime>
#include<cstdio>
#include<cstdlib>
#define maxn 100039
#define INF 0x3fffffff
inline int max(int x,int y){ return x>y?x:y; }
inline int min(int x,int y){ return x<y?x:y; }
using namespace std;
//#define debug
typedef int Type;
inline Type read(){
	Type sum=0;
	int flag=0;
	char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar();
	if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){
		sum=(sum<<1)+(sum<<3)+(c^48);
		c=getchar();
	}
	if(flag) return -sum;
	return sum;
}
struct JTZ{
	int val,siz,cnt,ls,rs,f;
}a[maxn];
int root,n;
int T;
int op,x;
void up(int rt){
	a[rt].siz=a[rt].cnt+a[a[rt].ls].siz+a[a[rt].rs].siz;
	return;
}
int newnode(int x){
	a[++n].val=x;
	a[n].cnt=1;
	a[n].f=rand();
	return n;
}

void zag(int &p){//right
	int k=a[p].ls;
	a[p].ls=a[k].rs; a[k].rs=p;
	p=k; up(a[p].rs); up(p);
	return;
}
void zig(int &p){//left
	int k=a[p].rs;
	a[p].rs=a[k].ls; a[k].ls=p;
	p=k; up(a[p].ls); up(p);
	return;
}
void insert(int &rt,int x){
	if(!rt) rt=newnode(x);
	else if(a[rt].val==x) a[rt].cnt++;
	else if(a[rt].val>x){
		insert(a[rt].ls,x);
		if(a[rt].f>a[a[rt].ls].f) zag(rt);
	}
	else{
		insert(a[rt].rs,x);
		if(a[rt].f>a[a[rt].rs].f) zig(rt);
	}
	up(rt);
	return;
}
void del(int &rt,int x){
	if(a[rt].val==x){
		if(a[rt].cnt>1)
		    a[rt].cnt--;
		else{
			if(!a[rt].ls) rt=a[rt].rs;
			else if(!a[rt].rs) rt=a[rt].ls;
			else if(a[a[rt].ls].f<a[a[rt].rs].f) zag(rt),del(a[rt].rs,x);
			else zig(rt),del(a[rt].ls,x);
		}
	}
	else if(a[rt].val<x) del(a[rt].rs,x);
	else del(a[rt].ls,x);
	up(rt);
}
int findrk(int rt,int x){
	if(!rt) return 0;
	if(a[rt].val==x) return a[a[rt].ls].siz+1;
	else if(a[rt].val>x) return findrk(a[rt].ls,x);
	else return findrk(a[rt].rs,x)+a[rt].cnt+a[a[rt].ls].siz;
}
int findval(int rt,int x){
	if(a[a[rt].ls].siz<x&&x<=a[a[rt].ls].siz+a[rt].cnt) return a[rt].val;
	else if(x<=a[a[rt].ls].siz) return findval(a[rt].ls,x);
    else return findval(a[rt].rs,x-a[a[rt].ls].siz-a[rt].cnt);
}
int findpre(int rt,int x){
	if(!rt) return -INF;
	if(x>a[rt].val)
	    return max(a[rt].val,findpre(a[rt].rs,x));
	return findpre(a[rt].ls,x);
}
int findnex(int rt,int x){
	if(!rt) return INF;
	if(x<a[rt].val)
	    return min(a[rt].val,findnex(a[rt].ls,x));
	return findnex(a[rt].rs,x);	
}
int dfs(int rt){
	if(!rt) return 0;
	return max(dfs(a[rt].ls),dfs(a[rt].rs))+1;
}
int main(){
	//freopen("P3369_12.in","r",stdin);
	//freopen("1.out","w",stdout);
    T=read(); srand(time(0));
    newnode(-INF); newnode(INF);
    a[1].f=-INF; a[2].f=INF;
    a[1].rs=2; up(1); up(2); root=1;
    int i=0; 
	while(T--){
        op=read(); x=read();
    	if(op==1) insert(root,x);
    	if(op==2) del(root,x);
    	if(op==3) printf("%d\n",findrk(root,x)-1);
    	if(op==4) printf("%d\n",findval(root,x+1));
    	if(op==5) printf("%d\n",findpre(root,x));
    	if(op==6) printf("%d\n",findnex(root,x));
	}
	//printf("%d",dfs(root));
	return 0;
}

关于效率

显然,树高越低效率越高。
有些时候平衡树写假了,当然答案肯定是对的,但是因为数据水所以我们可能会卡过去,所以我们首先到最后一个数据,然后运行一遍在检验树高(也就是 dfs 函数)就可以看看平衡树是否写假了,树高应该是略大于 \(\log_2n\)

posted @ 2021-02-26 09:31  jiangtaizhe001  阅读(57)  评论(0编辑  收藏  举报