[笔记]AVL树
AVL树是一种严格平衡的二叉搜索树,任何操作结束后,都能保证每个节点的左右子树高度相差不超过\(1\)。
内容源自BV1rt411j7Ff - 【AgOHの数据结构】平衡树专题之叁 树旋转与AVL树。
模板题:P3369 【模板】普通平衡树。
结构体定义 & 基本函数
struct node{
int l;//左孩子
int r;//右孩子
int v;//值
int hei;//高度,叶节点为1
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;//左+右+1
avl[u].hei=max(avl[avl[u].l].hei,avl[avl[u].r].hei)+1;//max(左,右)+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);//LL
else lrot(avl[u].l),rrot(u);//LR
}else if(uf<-1){
int rf=factor(avl[u].r);
if(rf<0) lrot(u);//RR
else rrot(avl[u].r),lrot(u);//RL
}else if(u) update(u);//如果原本就平衡,且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);//自下向上更新节点信息&调整结构
}
//找u的后继(即u先往右走,再不断往左直到没有左子结点)v,
//让v的父节点直接连接v的右子树
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);//u的后继v来替代u的位置
avl[u].l=l;//v成为子树的根,连接左边
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);//自下向上更新节点信息&调整结构
}
//计算v的排名(小于v的个数+1)
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;
}
//计算第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);//LL
else lrot(avl[u].l),rrot(u);//LR
}else if(uf<-1){
int rf=factor(avl[u].r);
if(rf<=0) lrot(u);//RR
else rrot(avl[u].r),lrot(u);//RL
}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);//找u的后继,即比u大的第一个数
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);//LL
else lrot(avl[u].l),rrot(u);//LR
}else if(uf<-1){
int rf=factor(avl[u].r);
if(rf<=0) lrot(u);//RR
else rrot(avl[u].r),lrot(u);//RL
}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);//找u的后继,即比u大的第一个数
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){//小于自己的个数+1
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;//如果ran在[siz[l]+1,siz[l]+cnt[u]]的区间内,就说明第ran名就是u
}
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。
似乎相同节点合并反而更慢?
[笔记]AVL树的更多相关文章
- 《编程珠玑,字字珠玑》读书笔记完结篇——AVL树
写在最前面的 手贱翻开了<珠玑>的最后几章,所以这一篇更多是关于13.14.15章的内容.这篇文章的主要内容是“AVL树”,即平衡树,比红黑树低一个等次.捣乱真惹不起红黑树,情况很复杂:而 ...
- 【动画笔记】数据结构-AVL树的插入操作
本笔记前置知识: 二叉搜索(排序)树及其插入操作. 本文主要围绕AVL树的平衡因子.纸上做题思路.失衡类型(LL/RR/LR/RL).失衡调整方法.插入后回溯这几部分知识点展开. 注: 本笔记中的平衡 ...
- 二叉树学习笔记之经典平衡二叉树(AVL树)
二叉查找树(BSTree)中进行查找.插入和删除操作的时间复杂度都是O(h),其中h为树的高度.BST的高度直接影响到操作实现的性能,最坏情况下,二叉查找树会退化成一个单链表,比如插入的节点序列本身就 ...
- AVL树 - 学习笔记
2017-08-29 14:35:55 writer:pprp AVL树就是带有平衡条件的二叉查找树.每个节点的左子树和右子树高度相差最多为1的二叉查找树 空树的高度定为-1 对树的修正称为旋转 对内 ...
- 【数据结构与算法Python版学习笔记】树——平衡二叉搜索树(AVL树)
定义 能够在key插入时一直保持平衡的二叉查找树: AVL树 利用AVL树实现ADT Map, 基本上与BST的实现相同,不同之处仅在于二叉树的生成与维护过程 平衡因子 AVL树的实现中, 需要对每个 ...
- AVL树的算法思路整理
http://www.cnblogs.com/heqile/archive/2011/11/28/2265713.html 看完了<数据结构与算法分析(C++描述)>的4.4节AVL树,做 ...
- AVL树,红黑树,B-B+树,Trie树原理和应用
前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作 ...
- AVL树(查找、插入、删除)——C语言
AVL树 平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Land ...
- python常用算法(5)——树,二叉树与AVL树
1,树 树是一种非常重要的非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中树那样.树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形 ...
- 数据结构与算法——平衡二叉树(AVL树)
目录 二叉排序树存在的问题 基本介绍 单旋转(左旋转) 树高度计算 旋转 右旋转 双旋转 完整代码 二叉排序树存在的问题 一个数列 {1,2,3,4,5,6},创建一颗二叉排序树(BST) 创建完成的 ...
随机推荐
- 探秘Transformer系列之(35)--- 大模型量化基础
探秘Transformer系列之(35)--- 大模型量化基础 目录 探秘Transformer系列之(35)--- 大模型量化基础 0x00 概述 0x01 outlier 1.1 定义 1.2 特 ...
- Centos7.x系统Nvme SSD 软Raid删除
查看磁盘挂载和Raid信息 [root@host-10-105-36-41 ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 44 ...
- hot100之链表上
相交链表(160) 先看代码 public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode ...
- pg 随机函数 和uuid 生成
UUID生成 -- 创建 create extension "uuid-ossp" ; -- 使用 select uuid_generate_v4() -- 创建随机函数 CREA ...
- Hadoop入门学习总结系列文章目录
一.为何要学习Hadoop? 这是一个信息爆炸的时代.经过数十年的积累,很多企业都聚集了大量的数据.这些数据也是企业的核心财富之一,怎样从累积的数据里寻找价值,变废为宝炼数成金成为当务之急.但数据增长 ...
- GIM 1.5发布了! 支持Windows系统了
GIM 1.5 发布了,现在支持Windows系统使用了. 这样 GIM 就覆盖 Mac, Linux, Windows 三大平台了 新功能 本次更新给 prompt 命令增加了 --reset 选项 ...
- C# 对字符串进行UrlEncode/UrlDecode
https://www.cnblogs.com/li150dan/p/13492280.html //对字符进行UrlEncode编码 string text= System.Web.HttpUtil ...
- 安卓端-APPUI自动化实战【下】
上一篇介绍了在solopi端的二次开发内容,接下来介绍下服务端的实现原理. 框架介绍: 使用比较成熟封装度较高的开源框架,尽量减少二次开发难度:Pear Admin Boot: 基 于 Spring ...
- BIO 和 NIO AIO
简介 BIO Blocking IO 阻塞IO 简单来说, 就是服务器对每一个接收数据请求, 开启一个线程进行对于数据和逻辑的处理, 但是能创建的线程数量有限. 很多处理逻辑开启的线程处于阻塞状态. ...
- C++线程池 基于C的实现 学习1
简介 线程池是什么? 打饭的阿姨们 前去吃饭的人们,任务 管理组件 线程池由三部分组成 执行队列,线程s 任务队列,任务s 管理组件 类似于 银行营业厅 食堂打饭 每个打饭的人都是一个线程 管理制度 ...