「模板」「讲解」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也许不符合堆性质。
这时,我们要通过旋转操作维护堆性质。
具体操作为,以当前点为根,进行如下图所示的旋转。图源网络。侵删。
旋转示例。图为右旋,自绘。
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);
}
旋转后更新子树大小。
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;
}
结束语
模板这种东西,尤其是代码量大的,及时复习很重要。
希望我的讲解可以帮助到大家吧。
谢谢阅读。