Treap学习笔记
Treap(树堆) 学习笔记(此处为带旋Treap)
Treap简介
Treap是一种二叉搜索树,其中,权值val满足二叉搜索树的性质,节点优先级priority满足堆的性质(作用后面会讲到)
Treap适用情况
因为属于二叉搜索树,所以可以维护二叉搜索树的信息,带旋Treap可以更好地控制树的深度,使得每次操作不至于被特殊数据卡成一条链使得单次操作复杂度从logn退化到n
Treap维护的信息
- 左右儿子的编号(带修,不适用堆式存储) lc rc
- 权值 w
- 子树大小 siz
- 节点个数 cnt (可能有重复权值的节点)
- 优先级 pos (维护堆的性质,用随机化给出)
Treap基本操作
(先给出维护siz的代码)void pushup(int &x){ siz(x)=siz(lc(x))+siz(rc(x))+cnt(x); return ; }
1.旋转
为了维护树的深度,把树进行一定旋转保证操作复杂度。
旋转分为左旋和右旋(图自oi-wiki):
那么我们不难写出旋转部分的代码
void zag(int &x){//左旋
int y=rc(x);
rc(x)=lc(y);
lc(y)=x;
siz(y)=siz(x);
pushup(x);
x=y;
return ;
}
void zig(int &x){//右旋
int y=lc(x);
lc(x)=rc(y);
rc(y)=x;
siz(y)=siz(x);
pushup(x);
x=y;
return ;
}
2.插入节点
- 没有这个节点时要创建一个新节点并编号初始化
- 有这个节点就cnt++
- 维护旋转:如果插入的是节点的左子树,且这个节点的左儿子的优先级小(大)于当前节点(取决于维护的是大根堆还是小根堆),那就右旋使得优先级小的节点深度减小,可以理解为向上移动,否则如果插入的右子树,同理。有如下代码
void Insert(int &x,int val){
if(!x){
x=++tot;c(x)=val;
pos(x)=rand();//赋予随机优先级
return ;
}
if(val<c(x)){//小于当前节点,插入当前节点的左子树
Insert(lc(x),val);
if(pos(lc(x))<pos(x)) zig(x);//右旋
}
else{
Insert(rc(x),val);
if(pos(rc(x))<pos(x)) zag(x);//左旋
}
}
3.删除节点
主要是分类讨论:
- 节点有多个 cnt>1 只用cnt--就行了(维护siz)
2.有左子树和右子树,考虑要让谁来替换这个节点
void Delete(int &x,int val){
if(!x) return ;
if(w(x)==val){
if(cnt(x)>1){
cnt(x)--;
pushup(x);
return ;
}
if(!lc(x)||!rc(x)) x=lc(x)+rc(x);
else if(pos(lc(x))<pos(rc(x))) zig(x),Delete(x,val);//右旋使得左儿子成为父亲
else zag(x),Delete(x,val);
pushup(x);
return ;
}
siz(x)--;
if(val<w(x)) Delete(lc(x),val);
else Delete(rc(x),val);
pushup(x);
return ;
}
4.查询某个值的排名
因为Treap是一棵二叉搜索树,满足二叉搜索树的性质,所以权值小的在左子树中,权值大的在右子树中,需要注意的是如果进入右子树查找,排名要加上左子树的大小(因为左子树的所有值都比右子树小)
int queryrank(int val){
//查询val的排名
int x=root,p=0;
while(x){
if(w(x)==val) return p+siz(lc(x))+1;
if(w(x)>val) x=lc(x);
else p+=siz(lc(x))+cnt(x),x=rc(x);
}
return p;
}
5.查询排名为p的数
同上
int querykth(int p){
//查询排名为p的数
int x=root;
while(x){
if(siz(lc(x))<p && siz(lc(x))+cnt(x)>=p)
return w(x);
if(siz(lc(x))>=p) x=lc(x);
else p=p-siz(lc(x))-cnt(x),x=rc(x);
}
return 0;
}
6.查询x的前驱
从树根开始,一直向下找比x小的最大值
int query_pre(int val){
//前趋,定义为小于x,且最大的数
int x=root,pre=0;
while(x){
if(w(x)<val) pre=w(x),x=rc(x);
else x=lc(x);
}
return pre;
}
从树根开始,一直向下找比x大的最小值
7.查询x的后驱
int query_nxt(int val){
//后趋,定义为大于x,且最小的数
int x=root,nxt=0;
while(x){
if(w(x)>val) nxt=w(x),x=lc(x);
else x=rc(x);
}
return nxt;
}
完整代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int n,m,tot,root;
struct node{
int lc,rc,fa,w,pos,siz,cnt;
#define lc(x) tree[x].lc
#define rc(x) tree[x].rc
#define fa(x) tree[x].fa
#define w(x) tree[x].w
#define pos(x) tree[x].pos//随机优先级
#define siz(x) tree[x].siz
#define cnt(x) tree[x].cnt
}tree[N];
void pushup(int &x){
siz(x)=siz(lc(x))+siz(rc(x))+cnt(x);
return ;
}
void zag(int &x){//左旋
int y=rc(x);
rc(x)=lc(y);
lc(y)=x;
siz(y)=siz(x);
pushup(x);
x=y;
return ;
}
void zig(int &x){//右旋
int y=lc(x);
lc(x)=rc(y);
rc(y)=x;
siz(y)=siz(x);
pushup(x);
x=y;
return ;
}
void Insert(int &x,int val){
if(!x){//空节点
x=++tot;w(x)=val;siz(x)=cnt(x)=1;
lc(x)=rc(x)=0;pos(x)=rand();//赋予随机优先级
return ;
}
++siz(x);
if(w(x)==val){
++cnt(x);
pushup(x);
return ;
}
if(val<w(x)){//小于当前节点,插入当前节点的左子树
Insert(lc(x),val);
if(pos(lc(x))<pos(x)) zig(x);
}
else{
Insert(rc(x),val);
if(pos(rc(x))<pos(x)) zag(x);
}
pushup(x);
return ;
}
void Delete(int &x,int val){
if(!x) return ;
if(w(x)==val){
if(cnt(x)>1){
cnt(x)--;
pushup(x);
return ;
}
if(!lc(x)||!rc(x)) x=lc(x)+rc(x);
else if(pos(lc(x))<pos(rc(x))) zig(x),Delete(x,val);
else zag(x),Delete(x,val);
pushup(x);
return ;
}
siz(x)--;
if(val<w(x)) Delete(lc(x),val);
else Delete(rc(x),val);
pushup(x);
return ;
}
int query_pre(int val){
//前趋,定义为小于x,且最大的数
int x=root,pre=0;
while(x){
if(w(x)<val) pre=w(x),x=rc(x);
else x=lc(x);
}
return pre;
}
int query_nxt(int val){
//后趋,定义为大于x,且最小的数
int x=root,nxt=0;
while(x){
if(w(x)>val) nxt=w(x),x=lc(x);
else x=rc(x);
}
return nxt;
}
int queryrank(int val){
//查询val的排名
int x=root,p=0;
while(x){
if(w(x)==val) return p+siz(lc(x))+1;
if(w(x)>val) x=lc(x);
else p+=siz(lc(x))+cnt(x),x=rc(x);
}
return p;
}
int querykth(int p){
//查询排名为p的数
int x=root;
while(x){
if(siz(lc(x))<p && siz(lc(x))+cnt(x)>=p)
return w(x);
if(siz(lc(x))>=p) x=lc(x);
else p=p-siz(lc(x))-cnt(x),x=rc(x);
}
return 0;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
srand(time(0));
cin>>n;
int op,x;
while(n--){
cin>>op>>x;
if(op==1) Insert(root,x);
if(op==2) Delete(root,x);
if(op==3){//只要这里注意原树中可能没有这个节点,先插入再查询,最后删除
Insert(root,x);
cout<<queryrank(x)<<'\n';
Delete(root,x);
}
if(op==4) cout<<querykth(x)<<'\n';
if(op==5) cout<<query_pre(x)<<'\n';
if(op==6) cout<<query_nxt(x)<<'\n';
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)