【C++】AVL树


目录
1 简介
2 实现
2.1 框架构建
2.2 插入操作
2.2.1 平衡因子的更新
2.2.2 平衡因子异常时树的调整
3 检验
1 简介
AVL树基于二叉搜索树之上,又对其提出了平衡的要求,即:当向二叉搜索树插入新节点后,保证每个节点的左右子树高度之差的绝对值不超过1
AVL树具有如下性质:
1、它的左右子树都是二叉搜索树。
2、左右子树高度之差(简称平衡因子 = 右子树高度 - 左子树高度)的绝对值不超过1。
AVL树有多种方法来实现,使用平衡因子的方式只是其中一种,接下来讲解实现过程。
2 实现
2.1 框架构建
#pragma once
#include<iostream>template<class K, class V>
struct AVLTreeNode
{std::pair<K, V> _kv;AVLTreeNode<K, V>* _left; //左指针AVLTreeNode<K, V>* _right; //右指针AVLTreeNode<K, V>* _parent; //父指针int _bf; //balance factor 平衡因子
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://
private:Node* _root = nullptr;
};
2.2 插入操作
2.2.1 平衡因子的更新

//1、更新平衡因子转换成代码
//这里注意:最坏情况下,平衡因子要持续更新到根节点后停止while (parent){if (cur == parent->_left)--parent->_bf;else++parent->_bf;if (parent->_bf == 0)break;else if(parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_parent;}else if(parent->_bf == 2 || parent->_bf == -2){//调整树来减小平衡因子}else assert(false);}
2.2.2 平衡因子异常时树的调整

对于如何调整树,我们引入AVL树的旋转操作,AVL树的旋转分为4种
而旋转最终的目的:
1、让这颗子树左右高度差不超过1
2、旋转过程中让它继续保持是搜索树
3、更新调整孩子节点的平衡因子
4、让这棵树的高度跟插入前保持一致
情况1:新节点插入较深右子树的右侧---右右:左单旋

步骤:1、将值为60的节点的左子树移到值为30的节点的右指针下
2、再将以值为30的节点的树移到值为60的节点的左指针下

void RotateL(Node* parent){Node* sub = parent->_right;Node* subL = sub->_left;if (subL)subL->_parent = parent;Node* ppnode = parent->_parent;parent->_right = subL;sub->_left = parent;parent->_parent = sub;if (ppnode == nullptr){_root = sub;_root->_parent = nullptr;}else{if (ppnode->_right == parent)ppnode->_right = sub;else if (ppnode->_left == parent)ppnode->_left = sub;sub->_parent = ppnode;}//重新更新平衡因子sub->_bf = 0;parent->_bf = 0;}
情况2:新节点插入较深左子树的左侧---左左:右单旋

步骤:1、将值为30的节点的右子树移到值为60的节点的左指针下
2、再将以值为60的节点的树移到值为30的节点的右指针下
代码与左单旋类似
void RotateR(Node* parent){Node* sub = parent->_left;Node* subR = sub->_right;if (subR)subR->_parent = parent;Node* ppnode = parent->_parent;parent->_left = subR;sub->_right = parent;parent->_parent = sub;if (ppnode == nullptr){_root = sub;_root->_parent = nullptr;}else{if (ppnode->_left == parent)ppnode->_left = sub;else if (ppnode->_right == parent)ppnode->_right = sub;sub->_parent = ppnode;}sub->_bf = 0;parent->_bf = 0;}
情况3:新节点插入较高左子树的右侧---左右:先左单旋再右单旋 -- 左右双旋

步骤:先以30为轴进行左单旋,再以60为轴进行右单旋
void RotateLR(Node* parent){Node* sub = parent->_left;Node* subR = sub->_right;int bf = subR->_bf; //记录subR的_bf来判断是左插入还是右插入...RotateL(parent->_left);RotateR(parent);if (bf == -1) //subR左子树新增{sub->_bf = 0;parent->_bf = 1;subR->_bf = 0;}else if (bf == 1) //subR右子树新增{parent->_bf = 0;sub->_bf = -1;subR->_bf = 0;}else if (bf == 0) //subR自己就是新增{parent->_bf = 0;sub->_bf = 0;subR->_bf = 0;}elseassert(false);}
情况4:新节点插入较高右子树的左侧---右左:先右单旋再左单旋 -- 右左双旋
代码与左右双旋类似
void RotateRL(Node* parent){Node* sub = parent->_right;Node* subL = sub->_left;int bf = subL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 1){parent->_bf = -1;sub->_bf = 0;subL->_bf = 0;}else if (bf == 0){parent->_bf = 0;sub->_bf = 0;subL->_bf = 0;}else if (bf == -1){parent->_bf = 0;sub->_bf = 1;subL->_bf = 0;}elseassert(false);}
综上可得到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.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}elsereturn false;}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//更新平衡因子while (parent){if (cur == parent->_left)--parent->_bf;else++parent->_bf;if (parent->_bf == 0)break;else if (parent->_bf == -1 || parent->_bf == 1){cur = parent;parent = parent->_parent;}else if (parent->_bf == -2 || parent->_bf == 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);break;}}return true;}
3 检验
要检验一棵树是否为AVL树,可以先检验是否为二叉搜索树,再检验是否平衡树
如下附上代码:
//按照中序遍历打印,若为有序则是二叉搜索树
void _inorder(Node* root) {if (root == nullptr)return;_inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_inorder(root->_right);}
//检验是否为平衡二叉树
int getHeight(Node* root){if (root == nullptr)return 0;int lh = getHeight(root->_left);int rh = getHeight(root->_right);return lh > rh ? lh + 1 : rh + 1;}bool _isBalanced(Node* root){if (root == nullptr)return true;int lh = getHeight(root->_left);int rh = getHeight(root->_right);if (rh - lh != root->_bf)cout << "平衡因子异常" << endl;if (abs(lh - rh) > 2)return false;return _isBalanced(root->_left)&& _isBalanced(root->_right);}
本文着重讲解AVL数的整体构建过程,并未涉及到迭代器和其他等接口的设计,这些内容会在之后讲解红黑树一起加入。
感谢阅读
相关文章:
【C++】AVL树
目录 1 简介 2 实现 2.1 框架构建 2.2 插入操作 2.2.1 平衡因子的更新 2.2.2 平衡因子异常时树的调整 3 检验 1 简介 AVL树基于二叉搜索树之上,又对其提出了平衡的要求,即:当向二叉搜索树插入新节点后,保证每个节点的左右…...
Mybatis源码(2) - SqlSessionTemplate的介绍及创建过程
0. 前言1. Spring对SqlSessionTemplate的管理1.1. SqlSessionTemplate的创建:1.2. MapperProxy中sqlSession的来源:2. SqlSessionInterceptor中的getSqlSession0. 前言 众所周知😏:MyBatis通过SqlSessionFactory 创建SqlSession去调用Executo…...
女生做大数据有发展前景吗?
当前大数据发展前景非常不错,且大数据领域对于人才类型的需求比较多元化,女生学习大数据也会有比较多的工作机会。大数据是一个交叉学科涉及到的知识量比较大学习有一定的难度,女生比较适合大数据采集和大数据分析方向的工作岗位。 大数据采…...
Git实用指令记录
config 用例:对git最先要做的一个操作就是配置用户名和邮箱,否则无法commit查看所有可以config的条目,非常之多$ git config --list core.symlinksfalse core.autocrlftrue core.fscachetrue color.interactivetrue color.uiauto help.forma…...
复杂美公链技术重要特色:平行公链架构
复杂美公链技术Chain33从11月开源至今,获得众多合作方的认可,其中首创的平行公链架构被百度、阿里、360等机构认可并跟进研究,这也说明了平行公链或许是区块链普及应用的重要解决方案之一。 平行公链(以下简称平行链)是…...
Java——进制转换的一些内容
Java——进制转换的一些内容1.16进制字符串String转字节数组byte[]2.16进制字符串String转10进制数字int3.字节数组byte[]转字符串String4.16进制字符串String-->byte[]-->String(使用ByteBuffer转换)5.字节数组byte[]转字符数组char[]6.字节byte转…...
使用 Nodejs、Express、Postgres、Docker 在 JavaScript 中构建 CRUD Rest API
让我们在 JavaScript 中创建一个 CRUD rest API,使用:节点.js表达续集Postgres码头工人码头工人组成介绍这是我们将要创建的应用程序架构的架构:我们将为基本的 CRUD 操作创建 5 个端点:创造阅读全部读一个更新删除我们将使用以下…...
电子招标采购系统源码之什么是电子招投标系统?
随着互联网时代的到来,各行业都受到不同的影响,其中招投标行业也不例外。为了顺应互联网潮流的发展,电子招投标逐渐取代传统的纸质的招投标方式,给招标方、投标方、招标代理等各方也带来了前所未有的机遇与挑战。那么什么是电子招…...
匹配文件名称模块glob和fnmatch
匹配文件名称模块glob 1.概述 glob模式规则与re模块的正则表达式规则不大相同,glob模块遵循标准的UNIX路径扩展规则。 fnmatch模块用于根据glob模式比较文件名 2.glob表达式匹配文件名 2.1.测试文件 介绍glob配置规则前,先使用下面的代码创建测试文…...
day12_oop
今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、继承 三、重写 四、this和super 五、访问修饰符 零、 复习昨日 局部变量和成员变量什么区别 位置,作用域,初始值,内存位置,生命周期 构造方法…...
在 Flutter 中使用 webview_flutter 4.0 | js 交互
大家好,我是 17。 已经有很多关于 Flutter WebView 的文章了,为什么还要写一篇。两个原因: Flutter WebView 是 Flutter 开发的必备技能现有的文章都是关于老版本的,新版本 4.x 有了重要变化,基于 3.x 的代码很多要重…...
嵌入式ARM工业边缘计算机BL302的CAN总线接口如何设置?
CAN 接口如图所示,输入如下命令: ifconfig -a //查看所有网卡 如果 FlexCAN 驱动工作正常的话就会看到 CAN 对应的网卡接口,如图。从图中可 以看出,有一个名为“can0”的网卡,这个就是 BL302 板上的 CAN1 接口对应的 c…...
Win11系统如何安装Ubuntu20.04(WSL版本)并安装docker
终于还是下定决心去换电脑了……这次采用轻量级的WSL,发现虽然没有占内存的GUI界面,但是编码和阅读文档还是非常nice的 1、首先开启Win11的虚拟机服务 2、下载你期望的Ubuntu服务器(这里以20.04为例) 安装成功后,发现…...
Elasticsearch和Solr的区别
背景:它们都是基于Lucene搜索服务器基础之上开发,一款优秀的,高性能的企业级搜索服务器。(是因为他们都是基于分词技术构建的倒排索引的方式进行查询)开发语言:java语言开发诞生时间:Solr2004年…...
如何在北京买房
首先我陈述一点,如果为了买房后再卖掉赚取差价,我这篇文章也许不适合,我这篇文章为整体愿景的发展而设计,为可操作房产的买卖而操作。 买房的愿景: 首先,我们要以一种心态来买房。那就是以始为终的态度&am…...
使用Proxifier+burp抓包总结
一、微信小程序&网页抓包 1. Proxifier简介 Proxifier是一款功能非常强大的socks5客户端,可以让不支持通过代理服务器工作的网络程序能通过HTTPS或SOCKS代理或代理链。 2. 使用Proxifier代理抓包 原理:让微信相关流量先走127.0.0.1:80到burp。具体…...
安装华为aab包的处理方式
1、转换 aab包 为 apks 说明: 1、bundletool-all-1.11.2.jar 转换文件的工具 2、a.aab aab源文件 3、xxx.apks 导入的文件以及路径(例如:D:\Android\xxx.apks) 4、–ksxxxx.jks 该aab打包所需的jsk文件 5、三条命令为 jsk打包所…...
Word处理控件Aspose.Words功能演示:使用 C++ 将 RTF 文档转换为 PDF
Aspose.Words 是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和打印文档,而无需在跨平台应用程序中直接使用Microsoft Word。此外,API支持所有流行的Word处理文件…...
【Java|多线程与高并发】进程与线程的区别与联系
文章目录什么是进程什么是线程上下文切换多线程一定比串行执行快吗进程与线程的区别与联系什么是进程 进程的定义:进程是正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说CPU(寄存器),IO,内存&a…...
K8s手工创建kubeconfig
我们通过 kubectl 命令行连接 k8s apiserver 时需要依赖 kubeconfig 文件。 kubeconfig 文件通常包含了 context(上下文)列表,每个 context 又会引用 cluster 和 user,最后通过 current-context 指定当前 kubeconfig 使用哪个 con…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...
WPF八大法则:告别模态窗口卡顿
⚙️ 核心问题:阻塞式模态窗口的缺陷 原始代码中ShowDialog()会阻塞UI线程,导致后续逻辑无法执行: var result modalWindow.ShowDialog(); // 线程阻塞 ProcessResult(result); // 必须等待窗口关闭根本问题:…...
针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...
