zoukankan      html  css  js  c++  java
  • 「模板」「讲解」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
    ",T.Rank(rt,x));
    				break;
    			case 4:
    				printf("%d
    ",T.Xth(rt,x));
    				break;
    			case 5:
    				printf("%d
    ",T.Pre(rt,x));
    				break;
    			case 6:
    				printf("%d
    ",T.Next(rt,x));
    				break;
    		}
    	}
    	return 0;
    }
    

    结束语

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

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

    谢谢阅读。

  • 相关阅读:
    计算机网络 学习笔记-传输层:TCP协议简介
    C/C++里的const(2)
    C语言变量声明加冒号的用法
    CTL_CODE说明
    FreeImage.lib库的配置和简单使用 转
    WSASocket()与Socket()的区别 转
    Win7下运行VC程序UAC权限问题 VC2010设置UAC权限方法
    MFC通过URL下载并保存文件代码 转载
    opencv 数据类型转换:CvArr, Mat, CvMat, IplImage, BYTE 转
    ISIS Scanner Errors codes
  • 原文地址:https://www.cnblogs.com/Capella/p/7994711.html
Copyright © 2011-2022 走看看