普通平衡树
自己也是一位初学treap的新手,
大部分题解不太友好,直接放大段代码,把我看懵,
所以决定自己写篇题解
首先
平衡树满足以下性质
- 任一节点A的左子树的任一节点L的值不大于A的值
- 任一节点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;
}