数据结构——AVL树
文章目录
- 一.AVL树的定义
- 二.AVL树的插入
- 三.插入后更新平衡因子
- 四.AVL树的旋转
- 1.左单旋
- 2.右单旋
- 3.先左单旋再右单旋
- 4.先右单旋再左单旋
- 五.AVL树的性能分析
- 六.检查是否满足AVL树
- 七.源码
一.AVL树的定义
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵具有以下性质的二叉搜索树:
- 它的左右子树都是
AVL树 - 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
具有以上性质的树被称为AVL树。
如果一棵二叉搜索树是高度平衡的,它就是
AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)。
AVL树节点的定义:
template<class K,class V>
struct AVLTreeNode
{ALVTreeNode<K,V>* _left;AVLTreeNode<K,V>* _right;AVLTreeNode<K,V>* _parent;//父亲节点pair<K, V> _kv;//构造函数AVLTreeNode(const pair<K,V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}int _bf;//平衡因子
};
AVL树的定义:
template<class K,class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:private:Node* _root=nullptr;
};
二.AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
- 按照二叉搜索树的方式插入新节点
- 调整节点的平衡因子
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.second){//当前值小于要插入的值,往右边走parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.second){//当前值大于要插入的值,往左边走parent = cur;cur = cur->_left;}else{//有相同的值了,退出插入return false;}}//当cur走到了nullptr,就是找到了要插入的点了cur = new Node(kv);//判断插入在左边还是右边if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//确定父子关系//…………//更新插入后的平衡因子//…………
}
三.插入后更新平衡因子
新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树的平衡性
更新平衡因子的规则:
- 新增在右边,则会让父平衡因子
++,新增在左边,父平衡因子-- - 更新后,如果
parent->bf == 0,说明parent插入前的平衡因子是1 or -1,插入填上了矮的一边,parent的子树高度不变,不需要继续往上更新。 - 更新后,如果
parent->bf为1或-1, 说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,parnet高度变了,需要继续往上更新。 - 更新后,如果
parent->bf == 2或-2,说明parent插入前的平衡因子是1 or -1,已经到达平衡临界值,parent子树进行旋转处理将树保持平衡。 - 更新后,如果
parent->bf >2或<-2,则说明插入前树已经失去的平衡,要进行代码的检查。
while (parent)
{//更新平衡因子if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){//没有新增高度break;}else if(abs(parent->_bf)==1){//平衡因子为1,往上面继续找parent = parent->_parent;cur = cur->_parent;}else if (abs(parent->_bf) == 2){//需要旋转了}
}
四.AVL树的旋转
1.左单旋

新节点插入较高右子树的右侧—右右:左单旋

- 将
subR作为一个根节点 - 将
subRL作为parent的右节点(如果subRL存在的话) parent作为subR的左节点。
左旋的条件是
parent->_bf==2&&cur->_bf==1
旋转之后parent的平衡因子为0,subL的平衡因子也是0。
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL){subRL->_parent = subR;}subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;;}subR->_parent = pparent;}subR->_bf = parent->_bf = 0;
}
2.右单旋

新节点插入较高左子树的左侧—左左:右单旋

- 将
subL作为一个根节点 - 将
subLR作为parent的左节点(如果subLR存在的话) parent作为subL的右子节点。
右旋的条件是
parent->_bf==-2&&cur->_bf==-1
旋转之后parent的平衡因子为0,subL的平衡因子也是0。
3.先左单旋再右单旋

新节点插入较高左子树的右侧—左右:先左单旋再右单旋
如果将节点插入到c当中,平衡因子就会发生改变,所以这里的平衡因子需要分情况讨论。
这里通过subLR的平衡因子来确定是在左边插入还是在右边插入。
两种情况下subLR都是0。

void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL-> _right;int bf = subLR->_bf;//提前存好,旋转后会subLR会发生改变RotateL(parent->_left);RotateR(parent);subLR->_bf = 0;if (bf == 1){//在右边插入parent->_bf = 0;subL->_bf = -1;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;}else if (bf == 0){//已经平衡了parent->_bf = 0;subL->_bf = 0;}else{//插入存在问题assert(false);}
}
4.先右单旋再左单旋

新节点插入较高右子树的左侧—右左:先右单旋再左单旋
C增加节点之后高度和d一样都是h,将其全部旋转到右边去,然后再通过左旋把30压下去,将60作为根节点。
与左右单旋一样,插入的b还是c需要分别更新平衡因子
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);subRL->_bf = 0;if (bf == 1){subR->_bf = 0;parent->_bf = -1;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;}else if (bf == 0){parent->_bf = 0;subR->_bf = 0;}else{assert(false);}
}
五.AVL树的性能分析
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这
样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
六.检查是否满足AVL树
通过计算左右子树的高度差来确定是否满足AVL树,因为平衡因子是自己设置的,如果还通过平衡因子来确定的话会不太准。
bool _IsBalance(Node* root)
{if (root == nullptr){return true;}int leftHT = Height(root->_left);int rightHT = Height(root->_right);int diff = rightHT - leftHT;if (diff != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return abs(diff) < 2&& _IsBalance(root->_left)//递归左子树&& _IsBalance(root->_right);//递归右子树
}int Height(Node* root)
{if (root == nullptr)return 0;int left = Height(root->_left);int right = Height(root->_right);return max(left, right) + 1;
}
七.源码
namespace dianxia
{//树的节点template<class K, class V>struct AVLTreeNode{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf; // 平衡因子AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}};template<class K, class V>class AVLTree{typedef AVLTreeNode<K, V> Node;public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 1 || parent->_bf == -1){// 继续更新祖先parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0){break;}else if (parent->_bf == 2 || parent->_bf == -2){// 需要旋转处理 -- 1、让这颗子树平衡 2、降低这颗子树的高度 //左单旋if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}//右单旋else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}//左右双旋else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}//右左双旋else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else{assert(false);}break;}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}int Height(){return _Height(_root);}private:int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _IsBalance(Node* root){if (root == NULL){return true;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);if (rightH - leftH != root->_bf){cout << root->_kv.first << "节点平衡因子异常" << endl;return false;}return abs(leftH - rightH) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = subR->_bf = 0;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = parent->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 1){parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1){parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}else{assert(false);}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
}
本文到此结束,码文不易,还请多多支持哦!!!
相关文章:
数据结构——AVL树
文章目录 一.AVL树的定义二.AVL树的插入三.插入后更新平衡因子四.AVL树的旋转1.左单旋2.右单旋3.先左单旋再右单旋4.先右单旋再左单旋 五.AVL树的性能分析六.检查是否满足AVL树七.源码 一.AVL树的定义 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉…...
AI写作宝有哪些,分享两种AI写作工具
AI写作宝是一种基于人工智能技术的写作辅助工具。它可以根据用户输入的关键词和主题快速生成文章。AI写作宝可以为用户节省大量的时间和精力,帮助用户快速生成高质量的文章。今天就为大家推荐两款AI写作宝: 一、AI创作家 AI创作家是一款基于人工智能技…...
【uniapp 控制页面滑动速度】
可以使用 uni-app 提供的 onTouchMove 事件来控制页面滑动速度。 可以在 onTouchMove 事件方法中使用 event.deltaY 计算页面滑动的速度,然后根据需要来调整速度值,最后通过 event.preventDefault() 阻止默认的滑动行为,从而实现控制页面滑动…...
7-24 整数的分类处理 (20 分)
7-24 整数的分类处理 (20 分) 给定 N 个正整数,要求你从中得到下列三种计算结果: A1 能被 3 整除的最大整数 A2 存在整数 K 使之可以表示为 3K1 的整数的个数 A3 存在整数 K 使之可以表示为 3K2 的所有整数的平均值(精确到小数…...
MYSQL事务同时修改单条记录
疑问:Mysql多事务默认情况下,同时修改同一条记录运行修改吗?是否要手动加上for update行锁。 猜想:MySQL 会自动对涉及的数据行加上写锁(排他锁),以确保数据的一致性和隔离性。这是在默认的事务…...
安装skywalking并集成到微服务项目
文章目录 一、前言二、介绍1. 架构 三、安装skywalking服务端四、启动skywalking服务端五、微服务项目开发注册中心网关服务商品服务订单服务支付服务测试 六、下载java客户端七、微服务集成skywalking客户端1. idea启动2. 命令行启动3. 集成效果4. 服务实例5. 修改服务实例名称…...
一支笔,一双手,一道力扣(Leetcode)做一宿
文章目录 一、分享自己相关的经历二、分析可能存在的问题三、根据问题进行分解或建立思维导图四、分享好用的刷题网站并进行介绍 一、分享自己相关的经历 我是一名计算机专业的学生,之前在学习算法和数据结构时,对于简单题目还算能够顺利地刷过去。但是…...
Kubernetes(K8s)从入门到精通系列之九:使用kubeadm工具快速安装K8s集群
Kubernetes K8s从入门到精通系列之九:使用kubeadm工具快速安装K8s集群 一、安装kubeadm二、修改kubeadm的默认配置三、下载K8s相关镜像四、运行kubeadm imit命令安装Master节点五、将新的Node加入集群六、安装CNI网络插件七、验证K8s集群是否工作正常八、搭建高可用K8s集群详细…...
RabbitMQ 教程 | 第11章 RabbitMQ 扩展
👨🏻💻 热爱摄影的程序员 👨🏻🎨 喜欢编码的设计师 🧕🏻 擅长设计的剪辑师 🧑🏻🏫 一位高冷无情的编码爱好者 大家好,我是 DevO…...
一分钟完成centos7安装docker
action: 1、下载安装包2、安装docker 1、背景 使用CentOS / Redhat 7 版本的应该偏多。但是,Docker CE在系统中安装的时候,往往会出现一堆依赖包的报错,解决依赖包需要耗费不短的时间。 经验证,目前已找到兼容能力强的版本&am…...
NativePHP:使用PHP构建跨平台桌面应用的新框架
NativePHP是一个用于使用PHP构建桌面应用的框架。它允许PHP开发人员使用熟悉的工具和技术创建跨平台的原生应用。NativePHP具有一系列易于使用的类,一套用于构建和打包应用程序的工具以及一个静态跨平台PHP运行时。 官网地址:https://nativephp.comNati…...
删除这4个文件夹,流畅使用手机无忧
在现代社会中,手机已经成为我们生活中不可或缺的一部分。然而,随着使用时间的增长,我们可能会遇到手机卡顿和内存不足的问题,让我们感到十分困扰。手机卡顿不仅影响使用体验,还可能导致应用程序运行缓慢,甚…...
使用Bert预训练模型处理序列推荐任务
最近的工作有涉及该任务,整理一下思路以及代码细节。 流程 总体来说思路就是首先用预训练的bert模型,在训练集的序列上进行CLS任务。对序列内容(这里默认是token id的sequence)以0.3左右的概率进行随机mask,然后将相…...
将word每页页眉单独设置
在进行论文排版的时候,总是会出现页眉的页码设置问题,比如出现奇数或偶数页码一致,尝试将前面页码改掉,后面再修改前面也进行了变动,将每页页眉单独设置: (1)在第一页的最后一行输入…...
rust怎么生成随机数?
关注我,学习Rust不迷路!! 在 Rust 中,有几种不同的方法可以实现随机数生成。以下是其中几种常见的方法,以及它们的优缺点: 1. 使用 rand crate: 优点: rand crate 是 Rust 中最常…...
python-Excel数据模型文档转为MySQL数据库建表语句(需要连接数据库)-工作小记
将指定Excel文档转为create table 建表语句。该脚本适用于单一且简单的建表语句 呈现效果 代码 # -*- coding:utf-8 -*- # Time : 2023/8/2 17:50 # Author: 水兵没月 # File : excel_2_mysql建表语句.py import reimport pandas as pd import mysql.connectordb 库名mydb m…...
406 · 和大于S的最小子数组
链接:LintCode 炼码 - ChatGPT!更高效的学习体验! 题解:同向双指针 九章算法 - 帮助更多程序员找到好工作,硅谷顶尖IT企业工程师实时在线授课为你传授面试技巧 class Solution { public:/*** param nums: an array …...
xray的 webhook如何把它Hook住?^(* ̄(oo) ̄)^
xray webhook xray可以通过webhook传递扫描信息,官方文档也是一笔带过,可能大多数人都不清楚,或者仅仅知道有这么个东西,但是不知道怎么使用,webhook是xray被动监听模式下的一种输出结构和方式。相比输出Json和txt格式…...
浅析RabbitMQ死信队列
原文首发于公众号【CSJerry】 在现代分布式系统中,消息队列扮演着至关重要的角色。它们可以实现应用程序之间的异步通信,并确保数据的可靠传输和处理。而在这个领域中,RabbitMQ作为一种强大而受欢迎的消息队列解决方案,具备了高…...
ELK 企业级日志分析系统(ElasticSearch、Logstash 和 Kiabana 详解)
目录 一.ELK简介 1.1ELK的概述 1.2ELK的组成 1.2.1 ElasticSearch 1.2.2 Logstash 1.2.3 Kibana 1.2.4 小总结 1.3可以添加其他组件 1.4filebeat 结合 logstash 带来好处 1.5日志处理的步骤 二.Elasticsearch 2.1Elasticsearch概述 2.2Elasticsearch核心概念 2.2.1接近…...
如何组合seo关键词
如何组合SEO关键词 在当今的数字营销环境中,如何组合SEO关键词成为了每一个网站运营者的首要任务。这不仅决定了网站的可见度,还直接影响到流量和最终的转化率。本文将详细探讨如何组合SEO关键词,以实现最佳的搜索引擎优化效果。 什么是SEO…...
GHelper完整指南:为华硕笔记本卸载臃肿控制软件的最佳替代方案
GHelper完整指南:为华硕笔记本卸载臃肿控制软件的最佳替代方案 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, S…...
qt模块学习记录
qt模块学习记录一、Qt Core其他模块都用到的核心非图形类二、Qt GUI 设计 GUI 界面的基础类,包括 OpenGL三、功能模块Qt Network 使网络编程更简单和轻便的类Qt SQL 使用 SQL 用于数据库操作的类Qt Multimedia 音频、视频、摄像头和广播功能的类四、老式界面Qt Widg…...
PhotoScan软件在无人机航测数据处理中的高效应用流程
1. 无人机航测数据处理入门指南 第一次接触无人机航测数据处理的同学可能会觉得这是个高大上的技术活,其实只要掌握了PhotoScan这个神器,处理起来比想象中简单得多。我刚开始接触时也走了不少弯路,现在把最实用的经验分享给大家。 PhotoScan是…...
模型量化基础知识 - PTQ - 训练后量化
文章目录一、PTQ 是什么二、PTQ 的标准流程(五大步骤)✅ Step 0:准备 FP 模型(Baseline)✅ Step 1:插入量化节点(Quantization Simulation)✅ Step 2:校准(Ca…...
OpenClaw二次开发:基于Qwen3.5-9B定制个性化技能模块
OpenClaw二次开发:基于Qwen3.5-9B定制个性化技能模块 1. 为什么需要自定义技能模块 去年冬天,我发现自己每天早晨都要手动查询天气来决定穿什么衣服。作为一个技术爱好者,我开始思考:能否让OpenClaw自动完成这个任务?…...
集萃智造全自动咖啡机器人:从研磨萃取到清洁运维,一站式商用解决方案
当下商用咖啡场景(连锁咖啡店、机场 / 高铁站、写字楼、无人零售区)普遍面临三大难题:人工成本持续上涨、高峰出杯效率不足、出品稳定性差、门店 24 小时运营难落地。传统半自动 / 全自动咖啡机依赖熟练咖啡师,单杯制作耗时、口味…...
电源防反接电路设计与工程实践
1. 电源防反接电路的必要性在工业自动化和嵌入式系统设计中,电源接反是一个常见但危害极大的问题。不同于消费电子产品使用标准化接口,许多工业设备需要现场接线,操作人员稍有不慎就可能接错电源极性。我曾参与过一个煤矿监控系统的项目&…...
基于合法无代码平台滥用的新型钓鱼攻击机理与防御体系研究
摘要 2026 年 3 月卡巴斯基实验室披露针对 Bubble.io 等正规无代码开发平台的恶意滥用钓鱼攻击,攻击者依托平台高信誉域名、SSL 证书与可视化开发能力,快速生成高仿真钓鱼页面,绕过传统邮件网关与终端检测,实现账号凭证、多因素认…...
自动驾驶控制-PIDLQR控制路径跟踪仿真 Simulink和Carsim联合仿真,横向控制...
自动驾驶控制-PID&LQR控制路径跟踪仿真 Simulink和Carsim联合仿真,横向控制为前馈反馈lqr,纵向为位置-速度双PID控制 对于减小误差,可以联合后轮转向/四轮转向算法(小店中有) 下图为Simulink模型截图,跟…...
