AVL树是一种严格平衡的二叉搜索树,任何操作结束后,都能保证每个节点的左右子树高度相差不超过\(1\)。
内容源自BV1rt411j7Ff - 【AgOHの数据结构】平衡树专题之叁 树旋转与AVL树。
模板题:P3369 【模板】普通平衡树。
结构体定义 & 基本函数
| struct node{ |
| int l; |
| int r; |
| int v; |
| int hei; |
| int siz; |
| }avl[N]; |
| int cnt; |
| int root; |
| |
| void newnode(int &u,int v){ |
| avl[u=++cnt].v=v; |
| avl[cnt].siz=1; |
| } |
| |
| void update(int u){ |
| avl[u].siz=avl[avl[u].l].siz+avl[avl[u].r].siz+1; |
| avl[u].hei=max(avl[avl[u].l].hei,avl[avl[u].r].hei)+1; |
| } |
左右旋转
AVL树用旋转来维护树的平衡。旋转分左旋和右旋:

| |
| void lrot(int &u){ |
| int r=avl[u].r; |
| avl[u].r=avl[r].l; |
| avl[r].l=u; |
| u=r; |
| update(avl[u].l),update(u); |
| } |
| |
| void rrot(int &u){ |
| int l=avl[u].l; |
| avl[u].l=avl[l].r; |
| avl[l].r=u; |
| u=l; |
| update(avl[u].r),update(u); |
| } |
接下来我们需要判断并处理AVL树的不平衡情况。
| |
| int factor(int u){ |
| return avl[avl[u].l].hei-avl[avl[u].r].hei; |
| } |
对于树上的节点\(u\)(假定\(u\)的子树都平衡),其不平衡状态有\(4\)种:
- LL:\(u\)的左子树过高,而左子节点的左子树较高。
处理方法:右旋一次\(u\)。
- LR:\(u\)的左子树过高,而左子结点的右子树较高。
处理方法:设\(v\)是\(u\)的左儿子,先左旋\(v\)(转化成LL),再右旋\(u\)。
- RR:\(u\)的右子树过高,而右子节点的右子树较高。
处理方法:左旋一次\(u\)。
- RL:\(u\)的右子树过高,而右子节点的左子树较高。
处理方法:设\(v\)是\(u\)的右儿子,先右旋\(v\)(转化成RR),再左旋\(u\)。
若左子节点的左右子树高度相同,则既可以归纳为LL,也可以作为LR考虑。右子节点同理。
| |
| void check(int &u){ |
| int uf=factor(u); |
| if(uf>1){ |
| int lf=factor(avl[u].l); |
| if(lf>0) rrot(u); |
| else lrot(avl[u].l),rrot(u); |
| }else if(uf<-1){ |
| int rf=factor(avl[u].r); |
| if(rf<0) lrot(u); |
| else rrot(avl[u].r),lrot(u); |
| }else if(u) update(u); |
| } |
其他操作
和普通的BST一样了。
| |
| void ins(int &u,int v){ |
| if(!u) newnode(u,v); |
| else if(v<avl[u].v) ins(avl[u].l,v); |
| else ins(avl[u].r,v); |
| check(u); |
| } |
| |
| |
| int find(int &u,int fa){ |
| int ans; |
| if(!avl[u].l){ |
| ans=u; |
| avl[fa].l=avl[u].r; |
| }else{ |
| ans=find(avl[u].l,u); |
| check(u); |
| } |
| return ans; |
| } |
| |
| void del(int &u,int v){ |
| if(v==avl[u].v){ |
| int l=avl[u].l,r=avl[u].r; |
| if(!l||!r) u=l+r; |
| else{ |
| u=find(r,r); |
| avl[u].l=l; |
| if(u!=r) avl[u].r=r; |
| } |
| }else if(v<avl[u].v) del(avl[u].l,v); |
| else del(avl[u].r,v); |
| check(u); |
| } |
| |
| int getrank(int v){ |
| int u=root,ran=1; |
| while(u){ |
| if(v<=avl[u].v) u=avl[u].l; |
| else{ |
| ran+=avl[avl[u].l].siz+1; |
| u=avl[u].r; |
| } |
| } |
| return ran; |
| } |
| |
| int getnum(int ran){ |
| int u=root; |
| while(u){ |
| if(avl[avl[u].l].siz+1==ran) break; |
| else if(avl[avl[u].l].siz>=ran) |
| u=avl[u].l; |
| else |
| ran-=avl[avl[u].l].siz+1,u=avl[u].r; |
| } |
| return avl[u].v; |
| } |
| |
| |
| int pre(int x){return getnum(getrank(x)-1);} |
| |
| int nex(int x){return getnum(getrank(x+1));} |
Code
点击查看代码
| #include<bits/stdc++.h> |
| #define int long long |
| #define N 100010 |
| using namespace std; |
| struct node{ |
| int l,r,v,hei,siz; |
| }avl[N]; |
| int t,cnt,root; |
| void newnode(int &u,int v){ |
| avl[u=++cnt].v=v; |
| avl[cnt].siz=1; |
| } |
| void update(int u){ |
| avl[u].siz=avl[avl[u].l].siz+avl[avl[u].r].siz+1; |
| avl[u].hei=max(avl[avl[u].l].hei,avl[avl[u].r].hei)+1; |
| } |
| int factor(int u){ |
| return avl[avl[u].l].hei-avl[avl[u].r].hei; |
| } |
| void lrot(int &u){ |
| int r=avl[u].r; |
| avl[u].r=avl[r].l; |
| avl[r].l=u; |
| u=r; |
| update(avl[u].l),update(u); |
| } |
| void rrot(int &u){ |
| int l=avl[u].l; |
| avl[u].l=avl[l].r; |
| avl[l].r=u; |
| u=l; |
| update(avl[u].r),update(u); |
| } |
| void check(int &u){ |
| int uf=factor(u); |
| if(uf>1){ |
| int lf=factor(avl[u].l); |
| if(lf>=0) rrot(u); |
| else lrot(avl[u].l),rrot(u); |
| }else if(uf<-1){ |
| int rf=factor(avl[u].r); |
| if(rf<=0) lrot(u); |
| else rrot(avl[u].r),lrot(u); |
| }else if(u) update(u); |
| } |
| void ins(int &u,int v){ |
| if(!u) newnode(u,v); |
| else if(v<avl[u].v) ins(avl[u].l,v); |
| else ins(avl[u].r,v); |
| check(u); |
| } |
| int find(int &u,int fa){ |
| int ans; |
| if(!avl[u].l){ |
| ans=u; |
| avl[fa].l=avl[u].r; |
| }else{ |
| ans=find(avl[u].l,u); |
| check(u); |
| } |
| return ans; |
| } |
| void del(int &u,int v){ |
| if(v==avl[u].v){ |
| int l=avl[u].l,r=avl[u].r; |
| if(!l||!r) u=l+r; |
| else{ |
| u=find(r,r); |
| avl[u].l=l; |
| if(u!=r) avl[u].r=r; |
| } |
| }else if(v<avl[u].v) del(avl[u].l,v); |
| else del(avl[u].r,v); |
| check(u); |
| } |
| int getrank(int v){ |
| int u=root,ran=1; |
| while(u){ |
| if(v<=avl[u].v) u=avl[u].l; |
| else{ |
| ran+=avl[avl[u].l].siz+1; |
| u=avl[u].r; |
| } |
| } |
| return ran; |
| } |
| int getnum(int ran){ |
| int u=root; |
| while(u){ |
| if(avl[avl[u].l].siz+1==ran) break; |
| else if(avl[avl[u].l].siz>=ran) |
| u=avl[u].l; |
| else |
| ran-=avl[avl[u].l].siz+1,u=avl[u].r; |
| } |
| return avl[u].v; |
| } |
| int pre(int x){return getnum(getrank(x)-1);} |
| int nex(int x){return getnum(getrank(x+1));} |
| signed main(){ |
| ios::sync_with_stdio(false); |
| cin.tie(nullptr); |
| cin>>t; |
| while(t--){ |
| int op,x; |
| cin>>op>>x; |
| if(op==1) ins(root,x); |
| else if(op==2) del(root,x); |
| else if(op==3) cout<<getrank(x)<<"\n"; |
| else if(op==4) cout<<getnum(x)<<"\n"; |
| else if(op==5) cout<<pre(x)<<"\n"; |
| else if(op==6) cout<<nex(x)<<"\n"; |
| } |
| return 0; |
| } |
附:相同节点合并写法
当时看完视频,想到是不是能把相同节点计数,存在一个节点中。
于是就写出下面的代码了。结构体多存了一个\(cnt\),然后newnode
、update
、ins
、del
、getrank
、getnum
函数需要做相应的修改。
点击查看代码
| #include<bits/stdc++.h> |
| #define int long long |
| #define N 100010 |
| using namespace std; |
| struct node{ |
| int l,r,v,hei,siz,cnt; |
| }avl[N]; |
| int t,cnt,root; |
| void newnode(int &u,int v){ |
| avl[u=++cnt].v=v; |
| avl[cnt].siz=1; |
| avl[cnt].cnt=1; |
| } |
| void update(int u){ |
| avl[u].siz=avl[avl[u].l].siz+avl[avl[u].r].siz+avl[u].cnt; |
| avl[u].hei=max(avl[avl[u].l].hei,avl[avl[u].r].hei)+1; |
| } |
| int factor(int u){ |
| return avl[avl[u].l].hei-avl[avl[u].r].hei; |
| } |
| void lrot(int &u){ |
| int r=avl[u].r; |
| avl[u].r=avl[r].l; |
| avl[r].l=u; |
| u=r; |
| update(avl[u].l),update(u); |
| } |
| void rrot(int &u){ |
| int l=avl[u].l; |
| avl[u].l=avl[l].r; |
| avl[l].r=u; |
| u=l; |
| update(avl[u].r),update(u); |
| } |
| void check(int &u){ |
| int uf=factor(u); |
| if(uf>1){ |
| int lf=factor(avl[u].l); |
| if(lf>=0) rrot(u); |
| else lrot(avl[u].l),rrot(u); |
| }else if(uf<-1){ |
| int rf=factor(avl[u].r); |
| if(rf<=0) lrot(u); |
| else rrot(avl[u].r),lrot(u); |
| }else if(u) update(u); |
| } |
| void ins(int &u,int v){ |
| if(!u) newnode(u,v); |
| else if(v==avl[u].v) avl[u].cnt++; |
| else if(v<avl[u].v) ins(avl[u].l,v); |
| else ins(avl[u].r,v); |
| check(u); |
| } |
| int find(int &u,int fa){ |
| int ans; |
| if(!avl[u].l){ |
| ans=u; |
| avl[fa].l=avl[u].r; |
| }else{ |
| ans=find(avl[u].l,u); |
| check(u); |
| } |
| return ans; |
| } |
| void del(int &u,int v){ |
| if(v==avl[u].v){ |
| if(avl[u].cnt>1) avl[u].cnt--; |
| else{ |
| int l=avl[u].l,r=avl[u].r; |
| if(!l||!r) u=l+r; |
| else{ |
| u=find(r,r); |
| avl[u].l=l; |
| if(u!=r) avl[u].r=r; |
| } |
| } |
| }else if(v<avl[u].v) del(avl[u].l,v); |
| else del(avl[u].r,v); |
| check(u); |
| } |
| int getrank(int v){ |
| int u=root,ran=1; |
| while(u){ |
| if(v<=avl[u].v) u=avl[u].l; |
| else{ |
| ran+=avl[avl[u].l].siz+avl[u].cnt; |
| u=avl[u].r; |
| } |
| } |
| return ran; |
| } |
| int getnum(int ran){ |
| int u=root; |
| while(u){ |
| int sz=avl[avl[u].l].siz+avl[u].cnt; |
| if(ran<=avl[avl[u].l].siz) u=avl[u].l; |
| else if(ran>avl[avl[u].l].siz+avl[u].cnt) ran-=sz,u=avl[u].r; |
| else break; |
| } |
| return avl[u].v; |
| } |
| int pre(int x){return getnum(getrank(x)-1);} |
| int nex(int x){return getnum(getrank(x+1));} |
| signed main(){ |
| ios::sync_with_stdio(false); |
| cin.tie(nullptr); |
| cin>>t; |
| while(t--){ |
| int op,x; |
| cin>>op>>x; |
| if(op==1) ins(root,x); |
| else if(op==2) del(root,x); |
| else if(op==3) cout<<getrank(x)<<"\n"; |
| else if(op==4) cout<<getnum(x)<<"\n"; |
| else if(op==5) cout<<pre(x)<<"\n"; |
| else if(op==6) cout<<nex(x)<<"\n"; |
| } |
| return 0; |
| } |
两种写法效率相当,不合并183ms,合并190ms。
似乎相同节点合并反而更慢?
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· [翻译] 为什么 Tracebit 用 C# 开发
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端