普通平衡树

自己也是一位初学treap的新手,

大部分题解不太友好,直接放大段代码,把我看懵,

所以决定自己写篇题解

首先

平衡树满足以下性质

  1. 任一节点A的左子树的任一节点L的值不大于A的值
  2. 任一节点A的右子树的任一节点R的值不小于A的值

显然二叉平衡树的中序遍历是非严格递增序列

并且一个序列所对应的二叉查找树并不唯一

一次操作的理想复杂度是logn

然鹅

也容易知道一颗二叉平衡树十分容易退化为一条链,
复杂度就容易变回n

所以

我们需要去维护ta,一种维护方式就是treap,Treap在每个节点拥有一个关键码时,同时赋予一个权值,权值的值为一个随机数(不要问我为什么)

然后,在维护关键码符合二叉查找树的性质的同时,
维护权值满足大根堆性质,
那么显然,这样的一棵树就会相对唯一,
这时,一颗treap就相对更难退化(有点玄学)

在讲操作前,先讲讲要存什么

#include<bits/stdc++.h>
using namespace std;
const int INF=0x7fffffff;
struct Treap{
	int lson,rson,sum,dat[1],size,length;
	#define ls(x) c[x].lson//左二子位置 
	#define rs(x) c[x].rson//右儿子位置 
	#define sum(x) c[x].sum//关键码 
	#define dat(x) c[x].dat//权值 
	#define sz(x) c[x].size//子树大小 
	#define ln(x) c[x].length//出现次数 
}c[100010];
int n,tmpx,flag,root,cnt;//cnt是树大小,root是根位置 

接下来一个一个操作来介绍

第一, 建节点

建节点:

int New(int val)//建节点 
{
	cnt++;
	sum(cnt)=val;
	dat(cnt)=rand();
	ln(cnt)=sz(cnt)=1;
	return cnt;
}

第二, 更新

要更新sz数组

更新:

void update(int p)//更新 
{
	sz(p)=sz(ls(p))+sz(rs(p))+ln(p);
}

第三, 建树

为了之后操作方便,建树时只含两个节点,INF与-INF

建树:

void build()//建树 
{
	New(-INF);
	New(INF);
	root=1;
	rs(1)=2;
	update(root);
}

第四, 旋转

尽管知道要去维护大根堆性质,但怎么维护?

就像维护一个堆一样,我们通过交换父子节点来维护treap,同时保证关键码依然符合平衡树性质

比如说,有这样一颗子树(图一)

    1
   ╱╲
  2  5
 ╱╲ 
3  4

我们要交换1,2节点

由定义知道,关键码符合3<2<4<1<5

我们把它旋转成这样(图二)

    2
   ╱╲
  3  1
 ╱╲
4  5

它仍然符合平衡树性质

我们把这一操作称作右旋(可以理解成将父节点旋到右子节点,以下称作zig)

那么,左旋也不难理解,可以看成图二到图一(称作zag)

旋转:

void zig(int &p)//右旋,&不可无 
{
	int q=ls(p);
	ls(p)=rs(q);
	rs(q)=p;
	p=q;
	update(rs(p));
	update(p);
}
void zag(int &p)//左旋,&不可无 
{
	int q=rs(p);
	rs(p)=ls(q);
	ls(q)=p;
	p=q;
	update(ls(p));
	update(p);
}

第五, 查找节点(与本题无直接联系)

当我们与当前节点比较,大于则向右走,小于则向左走,等于时就是找到了

第六, 添加节点

在第二的基础上,我们查找一个节点的值,如果找到了,说明此节点已存在,直接将这一节点的出现次数+1

如果找不到,也即当在一个节点打算向下走时,发现目标儿子节点不存在,那么就添加一个节点,并将父节点的儿子指针指向这一节点

在添加操作后,一定要判断是否要旋转父节点与新节点

添加节点:

void insert(int &p,int x)//添加节点,&不可无 
{
	if(p==0)
	{
		p=New(x);
		return;
	}
	if(x==sum(p))
	{
		ln(p)++;
		update(p);
		return;
	}
	if(x<sum(p))
	{
		insert(ls(p),x);
		if(dat(ls(p))>dat(p)) zig(p);
	}
	else
	{
		insert(rs(p),x);
		if(dat(rs(p))>dat(p)) zag(p);
	}
	update(p);
}

第七, 删除节点

类似三,我们查找到这一节点,如果它出现次数>1,那么直接将出现次数减一即可

如果出现次数出现次数等于1,我们需要将一个儿子节点旋上来

如果ta没有左儿子,或是左儿子的权值比右儿子小,那么我们将右儿子旋上来(左旋)

否则则右旋

删除节点:

void remove(int &p,int x)//删除节点,&不可无 
{
	if(p==0) return;
	if(sum(p)==x)
	{
		if(ln(p)>1)
		{
			ln(p)--;
			update(p);
			return;
		}
		if(ls(p)||rs(p))
		{
			if(rs(p)==0||dat(ls(p))>dat(rs(p)))
			{
				zig(p);
				remove(rs(p),x);
			}
			else
			{
				zag(p);
				remove(ls(p),x);
			}
			update(p);
		}
		else p=0;
		return;
	}
	x<sum(p) ? remove(ls(p),x) : remove(rs(p),x);
	update(p);
}

第八, 据值查排名

从第二的思想出发,如果向左走,那就向左走,

如果向右走,ans要加上此节点的出现次数以及左子节点的size

找到了这个故事就结束了

据值查排名:

int GetRankByVal(int p,int x)//据值查排名 
{
	if(p==0) return 0;
	if(sum(p)==x) return sz(ls(p));
	if(x<sum(p)) return GetRankByVal(ls(p),x);
	return GetRankByVal(rs(p),x)+sz(ls(p))+ln(p);
}

第九, 据排名查值

与六类似,排名为x,

也是从根节点出发如果(size of 左子树+出现次数 of 自身)<x 向右走,并使x=x-(size of 左子树+出现次数 of 此节点)

如果(size of 左子树+出现次数 of 此节点)>=x>size of 左子树,返回当前节点的值

如果size of 左子树>=x向左走

据排名查值:

int GetValByRank(int p,int rank)//据排名查值
{
	if(p==0) return INF;
	if(sz(ls(p))>=rank) return GetValByRank(ls(p),rank);
	if(sz(ls(p))+ln(p)>=rank) return sum(p);
	else GetValByRank(rs(p),rank-sz(ls(p))-ln(p));
}

第十, 求x的前驱

显然要从最小的往大的找啊,所以ans初始为-INF节点开始

然后从根节点开始往下找

类似查找的走法

但在途经任一节点时要更新ans(x未必被树所包含)

在找到了与x值相等的节点,就万事大吉,因为前驱是小于x的最大数

所以ans即是当前节点的左子树的最右端

求x的前驱:

int GetPre(int x)//求x的前驱
{
	int ans=1;
	int p=root;
	while(p)
	{
		if(x==sum(p))
		{
			if(ls(p)>0)
			{
				p=ls(p);
				while(rs(p)>0) p=rs(p);
				ans=p;
			}
			break;
		}
		if(sum(p)<x&&sum(p)>sum(ans)) ans=p;
		p=x<sum(p) ? ls(p) : rs(p);
	}
	return sum(ans);
}

第十一,求x的后继

与第七类似,只是ans初始为INF节点

求x的后继:

int GetNext(int x)//求x的后继
{
	int ans=2;
	int p=root;
	while(p)
	{
		if(sum(p)==x)
		{
			if(rs(p)>0)
			{
				p=rs(p);
				while(ls(p)>0) p=ls(p);
				ans=p;
			}
			break;
		}
		if(sum(p)>x&&sum(p)<sum(ans)) ans=p;
		p=x<sum(p) ? ls(p) : rs(p);
	}
	return sum(ans);
}

主程序:

int main()//主程序
{
	scanf("%d",&n);
	build();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&flag);
		if(flag==1)
		{
			scanf("%d",&tmpx);
			insert(root,tmpx);
		}
		if(flag==2)
		{
			scanf("%d",&tmpx);
			remove(root,tmpx);
		}
		if(flag==3)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetRankByVal(root,tmpx));
		}
		if(flag==4)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetValByRank(root,tmpx+1));
		}
		if(flag==5)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetPre(tmpx));
		}
		if(flag==6)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetNext(tmpx));
		}
	}
	return 0;
}

最后附上板子:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x7fffffff;
struct Treap{
	int lson,rson,sum,dat,size,length;
	#define ls(x) c[x].lson//左二子位置 
	#define rs(x) c[x].rson//右儿子位置 
	#define sum(x) c[x].sum//关键码 
	#define dat(x) c[x].dat//权值 
	#define sz(x) c[x].size//子树大小 
	#define ln(x) c[x].length//出现次数 
}c[100010];
int n,tmpx,flag,root,cnt;//cnt是树大小,root是根位置 
int New(int val)//建节点 
{
	cnt++;
	sum(cnt)=val;
	dat(cnt)=rand();
	ln(cnt)=sz(cnt)=1;
	return cnt;
}
void update(int p)//更新 
{
	sz(p)=sz(ls(p))+sz(rs(p))+ln(p);
}
void build()//建树 
{
	New(-INF);
	New(INF);
	root=1;
	rs(1)=2;
	update(root);
}
void zig(int &p)//右旋,&不可无 
{
	int q=ls(p);
	ls(p)=rs(q);
	rs(q)=p;
	p=q;
	update(rs(p));
	update(p);
}
void zag(int &p)//左旋,&不可无 
{
	int q=rs(p);
	rs(p)=ls(q);
	ls(q)=p;
	p=q;
	update(ls(p));
	update(p);
}
void insert(int &p,int x)//添加节点,&不可无 
{
	if(p==0)
	{
		p=New(x);
		return;
	}
	if(x==sum(p))
	{
		ln(p)++;
		update(p);
		return;
	}
	if(x<sum(p))
	{
		insert(ls(p),x);
		if(dat(ls(p))>dat(p)) zig(p);
	}
	else
	{
		insert(rs(p),x);
		if(dat(rs(p))>dat(p)) zag(p);
	}
	update(p);
}
void remove(int &p,int x)//删除节点,&不可无 
{
	if(p==0) return;
	if(sum(p)==x)
	{
		if(ln(p)>1)
		{
			ln(p)--;
			update(p);
			return;
		}
		if(ls(p)||rs(p))
		{
			if(rs(p)==0||dat(ls(p))>dat(rs(p)))
			{
				zig(p);
				remove(rs(p),x);
			}
			else
			{
				zag(p);
				remove(ls(p),x);
			}
			update(p);
		}
		else p=0;
		return;
	}
	x<sum(p) ? remove(ls(p),x) : remove(rs(p),x);
	update(p);
}
int GetRankByVal(int p,int x)//据值查排名 
{
	if(p==0) return 0;
	if(sum(p)==x) return sz(ls(p));
	if(x<sum(p)) return GetRankByVal(ls(p),x);
	return GetRankByVal(rs(p),x)+sz(ls(p))+ln(p);
}
int GetValByRank(int p,int rank)//据排名查值
{
	if(p==0) return INF;
	if(sz(ls(p))>=rank) return GetValByRank(ls(p),rank);
	if(sz(ls(p))+ln(p)>=rank) return sum(p);
	else GetValByRank(rs(p),rank-sz(ls(p))-ln(p));
}
int GetPre(int x)//求x的前驱
{
	int ans=1;
	int p=root;
	while(p)
	{
		if(x==sum(p))
		{
			if(ls(p)>0)
			{
				p=ls(p);
				while(rs(p)>0) p=rs(p);
				ans=p;
			}
			break;
		}
		if(sum(p)<x&&sum(p)>sum(ans)) ans=p;
		p=x<sum(p) ? ls(p) : rs(p);
	}
	return sum(ans);
}
int GetNext(int x)//求x的后继
{
	int ans=2;
	int p=root;
	while(p)
	{
		if(sum(p)==x)
		{
			if(rs(p)>0)
			{
				p=rs(p);
				while(ls(p)>0) p=ls(p);
				ans=p;
			}
			break;
		}
		if(sum(p)>x&&sum(p)<sum(ans)) ans=p;
		p=x<sum(p) ? ls(p) : rs(p);
	}
	return sum(ans);
}
int main()//主程序
{
	scanf("%d",&n);
	build();
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&flag);
		if(flag==1)
		{
			scanf("%d",&tmpx);
			insert(root,tmpx);
		}
		if(flag==2)
		{
			scanf("%d",&tmpx);
			remove(root,tmpx);
		}
		if(flag==3)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetRankByVal(root,tmpx));
		}
		if(flag==4)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetValByRank(root,tmpx+1));
		}
		if(flag==5)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetPre(tmpx));
		}
		if(flag==6)
		{
			scanf("%d",&tmpx);
			printf("%d\n",GetNext(tmpx));
		}
	}
	return 0;
}
posted @ 2022-05-30 21:08  chenguoyi  阅读(85)  评论(0编辑  收藏  举报