平衡树
平衡树
平衡:左右子树高度差的绝对值<=1称为平衡
用途:
1、插入一个数x
2、删除一个数x
3、查询一个数x(其排名,其前驱后继)
4、查询排名为k的数x
5、快速合并与分裂
6、维护区间修改、查询、翻转
7、维护其它信息
0)AVL:最普通的平衡树,维护[高度],只要出现了不平衡的条件,就通过左右单旋、双旋平衡
实际效果一般,但高度平衡
1)Treap:
一句话:符合堆与二叉搜索树性质,但弱平衡,实现较为简单
heap 堆:(小根堆) 父节点val值大于子节点
二叉搜索树: 左子树val全小于根节点,右子树val全大于根节点
两者实际上是矛盾的,但是可以用一个priority值维护堆得性质,用val维护二叉搜索树的性质
解决的问题:在数据不随机的情况下,二叉搜索树会退化成一条链,那么查询复杂度退化为O(n)
那么不妨利用堆随机化priority的属性,打乱节点插入顺序,使搜索树的复杂度期望值保持在log2n
1.有旋Treap
类似AVL分左旋右旋 在满足二叉搜索树的条件下根据堆的优先级对 treap 进行平衡操作。
有旋 treap 在做普通平衡树题的时候,是所有平衡树中常数较小的
用 treap 对每个节点定义一个由 rand 得到的权值,从而防止特殊数据卡
同时在每次删除/插入时通过这个权值决定要不要旋转即可,其他操作与二叉搜索树类似。
旋转:
左旋,就是把右子树变成根节点;右旋反之
旋转过后,跟旋转方向相同的子节点变成了原来的根节点
插入:
跟普通搜索树插入的过程没啥区别,但是需要在插的过程中 [通过旋转来维护树堆中堆的性质]
删除:
分类讨论,不同的情况有不同的处理方法,删完了树的大小会有变化,要注意更新
如果要删的节点有左子树和右子树,就要考虑删除之后让谁来当父节点(维护 priority 小的节点在上面)。
根据值查询排名:
查询以 cur 为根节点的子树中,val 值的大小的排名
根据排名查询值:
a.左子树 排名一定小于等于左子树的大小
b.根节点/当前节点 排名应该 >= 左子树的大小,并且<= 左子树的大小 + 根节点的重复次数
c.右子树 不然的话就在右子树
1 -> |左子树的节点|根节点|右子树的节点| -> n
^
要查的排名
转换成基于右子树的排名
1 -> |右子树的节点| -> n
^
要查的排名
转换方法就是直接把排名减去左子树的大小和根节点的重复数量
查询第一个比 val 小的节点
val 比当前节点值大的时候才会被更改的,返回这个变量就是返回 val 最后一次比当前节点的值大
查询第一个比 val 大的节点 同理,符号换了一下而已
My_code:
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int read() {
int x=0,f=1;
char ch=getchar();
while(ch<48||ch>57) {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>=48&&ch<=57) {
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
struct treap {
int l[N],r[N],val[N],pri[N],siz[N],w[N];
int sz,ans,rt;
inline void push_up(int x) {
siz[x]=siz[l[x]]+siz[r[x]]+w[x];
}
void lrotate(int &k) {
int t=r[k];
r[k]=l[t];
l[t]=k;
siz[t]=siz[k];
push_up(k);
k=t;
}
void rrotate(int &k) {
int t=l[k];
l[k]=r[t];
r[t]=k;
siz[t]=siz[k];
push_up(k);
k=t;
}
void insertx(int &k,int x) {
if(!k) {
sz++;
k=sz;
siz[k]=1;
w[k]=1;
val[k]=x;
pri[k]=rand();
return ;
}
siz[k]++;
if(val[k]==x) w[k]++;
else if(val[k]<x) {
insertx(r[k],x);
if(pri[r[k]]<pri[k]) lrotate(k);
} else {
insertx(l[k],x);
if(pri[l[k]]<pri[k]) rrotate(k);
}
}
bool del(int &k,int x) {
if(!k) return false;
if(val[k]==x) {
if(w[k]>1) {
w[k]--;
siz[k]--;
return true;
}
if(l[k]==0||r[k]==0) {
k=l[k]+r[k];
return true;
} else if(pri[l[k]]<pri[r[k]]) {
rrotate(k);
return del(k,x);
} else {
lrotate(k);
return del(k,x);
}
} else if(val[k]<x) {
bool can=del(r[k],x);
if(can) siz[k]--;
return can;
} else {
bool can=del(l[k],x);
if(can) siz[k]--;
return can;
}
}
int queryrank(int k,int x) {
if(!k) return 0;
if(val[k]==x) return siz[l[k]]+1;
else if(val[k]<x) return siz[l[k]]+w[k]+queryrank(r[k],x);
else return queryrank(l[k],x);
}
int querynum(int k,int x) {
if(!k) return 0;
if(x<=siz[l[k]]) return querynum(l[k],x);
else if(siz[l[k]]+w[k]<x) return querynum(r[k],x-siz[l[k]]-w[k]);
else return val[k];
}
void querypre(int k,int x) {
if(!k) return ;
if(val[k]<x) ans=k,querypre(r[k],x);
else querypre(l[k],x);
}
void querynxt(int k,int x) {
if(!k) return ;
if(val[k]>x) ans=k,querynxt(l[k],x);
else return querynxt(r[k],x);
}
} Tree;
int n;
int main() {
srand(time(NULL));
n=read();
while(n--) {
int op=read(),x=read();
if(op==1) Tree.insertx(Tree.rt,x);
if(op==2) Tree.del(Tree.rt,x);
if(op==3) {
int p=Tree.queryrank(Tree.rt,x);
printf("%d\n",p);
}
if(op==4) {
int p=Tree.querynum(Tree.rt,x);
printf("%d\n",p);
}
if(op==5) {
Tree.ans=0;
Tree.querypre(Tree.rt,x);
printf("%d\n",Tree.val[Tree.ans]);
}
if(op==6) {
Tree.ans=0;
Tree.querynxt(Tree.rt,x);
printf("%d\n",Tree.val[Tree.ans]);
}
}
return 0;
}
2.无旋Treap(FHQ Treap)
...
2)Splay
旋转谁就是说将哪个结点提升到其父结点的位置
单旋:1.左旋:右儿子向左旋 2.右旋:左儿子向右旋
双旋:根据当前结点 x,父结点 f 和祖父结点 g 的形态(如果没有祖父结点,那么做一次单旋就行了)来决定具体的旋转方案
异构调整:x,f(father),g(grand) 不在一条线上。此时操作和单旋一样,只需要将当前结点旋转两次即可
同构调整:x,f(father),g(grand) 在一条线上。此时我们需要先旋转 f,再旋转 x
双旋虽然不能把树变得非常平衡,但是均摊复杂度是正确的
splay:这个操作能够把一个结点 x 旋转到 top 结点的子结点的位置
(如果给定 top=0 则认为是旋转到根结点)
让 Splay 保持大致平衡
删除操作:首先,我们把当前结点 Splay 到根结点。
如果相同值的节点不止一个,那很简单,只要将值的数量减一就行了;
如果当前结点没有右儿子,也就是没有后继,那只需要将根节点的指针指到左儿子上就行了;
否则,我们找到当前结点的后继并将其 Splay 到当前结点的儿子结点处,此时这个后继一定没有左儿子。
于是我们直接将后继设为根结点,并 connect 后继和当前节点的左儿子就行了。
My_code:
//学习了番茄的splay
#include <bits/stdc++.h>
#define ls t[x].l
#define rs t[x].r
using namespace std;
const int inf=1e9;
const int N=2e5+10;
int read(){
int x=0,f=1;
char ch=getchar();
while(ch<48||ch>57){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>=48&&ch<=57){
x=(x<<3)+(x<<1)+(ch^48);
ch=getchar();
}
return x*f;
}
int n,op,q,rt,tot=0;
struct Splay{
int fa,val,cnt,siz,l,r;
}t[N];
int newnode(int x,int y){
t[++tot].fa=y;
t[tot].val=x;
t[tot].siz=t[tot].cnt=1;
return tot;
}
void to_clear(int x) {
t[x].fa=t[x].val=t[x].siz=t[x].cnt=ls=rs=0;
}
void push_up(int x){
to_clear(0);
t[x].siz=t[ls].siz+t[rs].siz+t[x].cnt;
}
void rotate(int x){
int f1=t[x].fa,f2=t[f1].fa;
if(f2){
if(t[f2].l==f1) t[f2].l=x;
else t[f2].r=x;
}
t[x].fa=f2;
t[f1].fa=x;
if(t[f1].l==x) t[f1].l=rs,t[rs].fa=f1,rs=f1;
else t[f1].r=ls,t[ls].fa=f1,ls=f1;
push_up(f1);
push_up(x);
}
void splay(int x){
while(t[x].fa){
int f1=t[x].fa,f2=t[f1].fa;
if(f2){
if((t[f1].l==x)^(t[f2].l==f1)) rotate(x);
else rotate(f1);
}
rotate(x);
}
rt=x;
}
void insert(int &x,int y,int fa){
if(!x) {
x=newnode(y,fa);
splay(x);
return ;
}
if(t[x].val==y) {
t[x].cnt++;
t[x].siz++;
return ;
}
if(t[x].val<y) insert(rs,y,x);
else insert(ls,y,x);
push_up(x);
}
int get_rank(int x,int y){
if(!x) return 0;
int rk=t[ls].siz;
if(t[x].val==y) {
splay(x);
return rk+1;
}
if(t[x].val<y) return get_rank(rs,y)+rk+t[x].cnt;
else return get_rank(ls,y);
}
void del(int x){
int rk=get_rank(rt,x);
rk=rt;
t[rk].cnt--;
if(t[rk].cnt) return ;
if(!t[rk].l&&!t[rk].r) to_clear(rk),rk=rt=0;
else if(!t[rk].l) rt=t[rk].r,t[rt].fa=0,to_clear(rk);
else if(!t[rk].r) rt=t[rk].l,t[rt].fa=0,to_clear(rk);
else {
int p=t[rk].l;
while(t[p].r) p=t[p].r;
splay(p);
t[p].r=t[rk].r;
t[t[rk].r].fa=p;
to_clear(rk);
}
push_up(rt);
}
int get_val(int x,int y){
if(t[ls].siz<y&&t[ls].siz+t[x].cnt>=y) return t[x].val;
if(t[ls].siz>=y) return get_val(ls,y);
else return get_val(rs,y-t[ls].siz-t[x].cnt);
}
int get_pre(int x,int y){
if(!x) return -inf;
if(t[x].val<y) return max(t[x].val,get_pre(rs,y));
else return get_pre(ls,y);
}
int get_nxt(int x,int y){
if(!x) return inf;
if(t[x].val>y) return min(t[x].val,get_nxt(ls,y));
else return get_nxt(rs,y);
}
int main() {
n=read();
for(int i=1;i<=n;i++){
op=read();
q=read();
if(op==1) insert(rt,q,0);
if(op==2) del(q);
if(op==3) printf("%d\n",get_rank(rt,q));
if(op==4) printf("%d\n",get_val(rt,q));
if(op==5) printf("%d\n",get_pre(rt,q));
if(op==6) printf("%d\n",get_nxt(rt,q));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?