「模板」「讲解」Treap名次树

Treap实现名次树

<题目链接>


前言

学平衡树的过程可以说是相当艰难。浏览Blog的过程中看到大量指针版平衡树,不擅长指针操作的我已经接近崩溃。于是,我想着一定要写一篇非指针实现的Treap的Blog。

具体如下。

简介

Treap(树堆,Tree+Heap)是一种强大的数据结构——每个节点除了本身键值(v)之外,附有一个随机优先级(p),其中v满足二叉搜索树性质,p满足堆性质(下文中为大根堆),通过旋转操作来维护性质,并使整棵树保持平衡。

名次树

顾名思义就是可以查找x的排名、查找第x名的值、查找前驱与后继的树。详见标题下方题目链接。

节点数据结构

struct node
{
	int v,p,size,c[2];
};//键值,优先级,(包括自身在内的)子树大小,左右子节点。

操作

每一种操作都是从根开始。

插入

首先, 与插入二叉搜索树一样。

给待插入节点一个随机的p值,为了避免重复,对生成随机数做了一些特殊处理。

int Random(void)
{
	int x;
	while(a[x=rand()%MAXN]);//a是bool数组,记录当前数是否被生成过,如果是,就重新生成。
	a[x]=1;
	return x;
}
  • 当前树为空,直接插入;
  • 待插入的v大于当前点的v,递归将当前点插入右子树;
  • 否则,递归将当前点插入左子树。

其次,待插入点的v到了合适的位置时,我们会发现它的p也许不符合堆性质。

这时,我们要通过旋转操作维护堆性质。

具体操作为,以当前点为根,进行如下图所示的旋转。图源网络。侵删。

0060lm7Tly1fmbhi3bn5gj30u40f7gm6.jpg

旋转示例。图为右旋,自绘。

void Rotate(int &i,bool p)
{
	int t=s[i].c[!p];
	s[i].c[!p]=s[t].c[p],s[t].c[p]=i;
	Update(i),Update(i=t);
}

0060lm7Tly1fmbikk6gtkj30nb07nglp.jpg

旋转后更新子树大小。

void Update(int i)
{
	s[i].size=s[s[i].c[0]].size+s[s[i].c[1]].size+1;
}

插入代码。

void Insert(int &i,int x)
{
	if(!i)
	{
		s[i=++cnt].v=x,s[i].p=Random(),s[i].size=1;
		return;
	}
	++s[i].size;
	bool t=x>s[i].v;
	Insert(s[i].c[t],x);
	if(s[s[i].c[t]].p>s[i].p)
		Rotate(i,!t);
}

删除

其实就是完全把插入的操作反过来。

  • 待删除的v等于当前点的v;
    • 两个子节点都不为空,则比较两边子节点,将较大的旋转上来,再删除;
    • 否则,当前点指向左右子节点中的非空节点(如果有),然后直接返回。
  • 待删除的v大于当前点的v时,递归右子树删除当前点;
  • 否则,递归左子树删除当前点。

操作完毕后更新当前点的子树大小。

void Erase(int &i,int x)
{
	if(x==s[i].v)
		if(s[i].c[0] && s[i].c[1])
		{
			bool t=s[s[i].c[0]].p>s[s[i].c[1]].p;
			Rotate(i,t),Erase(s[i].c[t],x);
		}
		else
		{
			i=s[i].c[0]|s[i].c[1];
			return;
		}
	else
		Erase(s[i].c[x>s[i].v],x);
	Update(i);
}

查找排名

  • 当前点为空,直接返回1;
  • 待查找的值大于当前点的值,递归在右子树中查找;
  • 否则,递归在左子树查找。
int Rank(int i,int x)
{
	return i ? (x>s[i].v ? Rank(s[i].c[1],x)+s[s[i].c[0]].size+1 : Rank(s[i].c[0],x)) : 1;
}

查找排名为x的数

  • 待查找的排名小于t=(当前点的左子树大小+1),递归查找左子树中排名为x的数;
  • 如果待查找的排名大于t,递归查找右子树中排名为x-t的数;
  • 否则,返回当前点的v。
int Xth(int i,int x)
{
	int t=s[s[i].c[0]].size+1;
	if(x<t)
		return Xth(s[i].c[0],x);
	else if(x>t)
		return Xth(s[i].c[1],x-t);
	else
		return s[i].v;
}

查找前驱

查找x的前驱,即查找整个Treap中比x小的最大数

  • 当前点为空,返回-INF;
  • 待查找的值大于当前点的v,说明待查找点一定在当前点之后,递归右子树,看是否可以找到比x小的更大数;
  • 否则,递归左子树,直到当前点在待查找点之前。
int Pre(int i,int x)
{
	return i ? (x>s[i].v ? max(Pre(s[i].c[1],x),s[i].v) : Pre(s[i].c[0],x)) : -INF;
}

查找后继

和查找前驱完全相反。

整体代码

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
using namespace std;
const int MAXN=100010,INF=10000010;
int n;
class Treap
{
	public:
		int rt;
		Treap(void)
		{
			rt=cnt=0;
			memset(a,0,sizeof a);
			memset(s,0,sizeof s);
		}
		void Insert(int &i,int x)
		{
			if(!i)
			{
				s[i=++cnt].v=x,s[i].p=Random(),s[i].size=1;
				return;
			}
			++s[i].size;
			bool t=x>s[i].v;
			Insert(s[i].c[t],x);
			if(s[s[i].c[t]].p>s[i].p)
				Rotate(i,!t);
		}
		void Erase(int &i,int x)
		{
			if(x==s[i].v)
				if(s[i].c[0] && s[i].c[1])
				{
					bool t=s[s[i].c[0]].p>s[s[i].c[1]].p;
					Rotate(i,t),Erase(s[i].c[t],x);
				}
				else
				{
					i=s[i].c[0]|s[i].c[1];
					return;
				}
			else
				Erase(s[i].c[x>s[i].v],x);
			Update(i);
		}
		int Rank(int i,int x)
		{
			return i ? (x>s[i].v ? Rank(s[i].c[1],x)+s[s[i].c[0]].size+1 : Rank(s[i].c[0],x)) : 1;
		}
		int Xth(int i,int x)
		{
			int t=s[s[i].c[0]].size+1;
			if(x<t)
				return Xth(s[i].c[0],x);
			else if(x>t)
				return Xth(s[i].c[1],x-t);
			else
				return s[i].v;
		}
		int Pre(int i,int x)
		{
			return i ? (x>s[i].v ? max(Pre(s[i].c[1],x),s[i].v) : Pre(s[i].c[0],x)) : -INF;
		}
		int Next(int i,int x)
		{
			return i ? (x<s[i].v ? min(Next(s[i].c[0],x),s[i].v) : Next(s[i].c[1],x)) : INF;
		}
	private:
		bool a[MAXN];
		int cnt;
		struct node
		{
			int v,p,size,c[2];
		}s[MAXN];
		int Random(void)
		{
			int x;
			while(a[x=rand()%MAXN]);
			a[x]=1;
			return x;
		}
		void Update(int i)
		{
			s[i].size=s[s[i].c[0]].size+s[s[i].c[1]].size+1;
		}
		void Rotate(int &i,bool p)
		{
			int t=s[i].c[!p];
			s[i].c[!p]=s[t].c[p],s[t].c[p]=i;
			Update(i),Update(i=t);
		}
}T;
int main(int argc,char *argv[])
{
	scanf("%d",&n);
	srand((unsigned)time(NULL));
	for(int i=1,&rt=T.rt,opt,x;i<=n;++i)
	{
		scanf("%d %d",&opt,&x);
		switch(opt)
		{
			case 1:
				T.Insert(rt,x);
				break;
			case 2:
				T.Erase(rt,x);
				break;
			case 3:
				printf("%d\n",T.Rank(rt,x));
				break;
			case 4:
				printf("%d\n",T.Xth(rt,x));
				break;
			case 5:
				printf("%d\n",T.Pre(rt,x));
				break;
			case 6:
				printf("%d\n",T.Next(rt,x));
				break;
		}
	}
	return 0;
}

结束语

模板这种东西,尤其是代码量大的,及时复习很重要。

希望我的讲解可以帮助到大家吧。

谢谢阅读。

posted @ 2017-12-06 20:44  Capella  阅读(331)  评论(3编辑  收藏  举报

谢谢光临