【C++进阶学习】第七弹——AVL树——树形结构存储数据的经典模块
二叉搜索树:【C++进阶学习】第五弹——二叉搜索树——二叉树进阶及set和map的铺垫-CSDN博客
目录
一、AVL树的概念
二、AVL树的原理与实现
AVL树的节点
AVL树的插入
AVL树的旋转
AVL树的打印
AVL树的检查
三、实现AVL树的完整代码
四、总结
前言:
在前面我们学习二叉搜索树的时候,虽然大部分情况下二叉搜索树的效率都是很高的,但是如果是一组相对有序的数字,我们用二叉搜索树来排序就会显得比较麻烦了,因此,AVL树就出现了,下面就让我们一起来学习以下AVL树的相关知识
一、AVL树的概念
AVL树实际上就是特殊的二叉搜索树,是对二叉搜索树的改进,我们在用树形结构来查找或操作数据的时候,一般都是要从树根一层一层往下找,所以树高越低,效率越高,但是二叉搜索树在有些场景下比较麻烦,比如一个相对有序的数组{2,1,3,4,5,6}
这样一个数组如果通过二叉搜索树处理就会显得层数太多,效率很低
所以人们也就有了这样一种思考:我们可不可以通过某种操作让左右子树的高度差不超过1,这样就能极大的提高效率,这就是AVL树的概念
AVL树中任何节点的左右子树的高度差(平衡因子)绝对值不超过1。这意味着树始终保持平衡,避免了二叉搜索树在节点插入或删除后可能出现的退化现象。
二、AVL树的原理与实现
了解了AVL树的基本内容之后,接下来我们就来一步一步学习以下AVL树的原理到底是什么以及如何实现一个AVL树:
AVL树的节点
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) //平衡因子起始值是0,当左子树插入一个节点时-1,右子树插入一个节点时+1{}
};
AVL树的节点操作与二叉搜索树还是比较相似的,都有左子树右子树和父亲节点的叉式结构,比较不同的是加入了一个平衡因子
AVL树的插入
实现AVL树的重点就是解决AVL树的插入问题,而解决插入问题最关键的就是要做到如何让左右子树高度的绝对值适合不大于1,我们是通过合理的旋转来实现的,而且需要旋转的情况也是分为四种的:
RR型:左旋
LL型:右旋
RL型:先右旋,再左旋
LR型:先左旋,再右旋
下面我们来看这样几个例子:
1、RR型(左旋)
2、LL型(右旋)
3、LR型(先左旋,再右旋)
4、RL型(先右旋,再左旋)
操作与3正好相反,不过多赘述
下面就让我们看一下插入的代码:
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->_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);break;}else if (parent->_bf == -2 && cur->_bf == -1){//右单旋RotateR(parent);break;}else if (parent->_bf == 2 && cur->_bf == -1){//RL型RotateRL(parent);break;}else if (parent->_bf == -2 && cur->_bf == 1){//LR型RotateLR(parent);break;}}else{assert(false);}}return true;}
AVL树的旋转
void RotateL(Node* parent) //左单旋(RR型){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;parent->_right = subRL;subR->_left = parent;if(subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_parent = subR;parent->_bf = 0;subR->_bf = 0;}void RotateR(Node* parent) //右单旋(LL型){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;parent->_left = subLR;subL->_right = parent;if (subLR){subLR->_parent = parent;}if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_parent = subL;subL->_bf = parent->_bf = 0;}void RotateRL(Node* parent) //先右单旋,再左单旋(RL型){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){//subRL自己就是新增点parent->_bf = subR->_bf = 0;}else if (bf == -1){//subRL的左子树上新增parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 1){//subRL的右子树上新增parent->_bf = -1;subRL->_bf = 0;subR->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent) //先左单旋,再右单旋(LR型){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){//subLR自己就是新增点parent->_bf = subL->_bf = 0;}else if (bf == -1){//subLR的左子树上新增parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0;}else if (bf == 1){//subLR的右子树上新增parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else{assert(false);}}
AVL树的打印
AVL树的打印与二叉搜索树一致,都是中序打印,我们就直接来看代码了
//中序打印void Inorder(){_Inorder(_root);cout << endl;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}
AVL树的检查
检查是否为AVL树,一方面我们可以通过打印的结果先来判断一下它是不是二叉搜索树,然后我们可以通过比较左右子树的高度差来判断它是否为AVL树(根据前面可知AVL树左右子树高度差最大为1)
//检查是否为AVL树bool IsBalance(){return _IsBalance(_root);}int _High(Node* root){if (root == nullptr)return 0;int LeftHigh = _High(root->_left);int RightHigh = _High(root->_right);return LeftHigh > RightHigh ? LeftHigh + 1 : RightHigh + 1;}bool _IsBalance(Node* root){if (root == nullptr)return true;int LeftHigh = _High(root->_left);int RightHigh = _High(root->_right);if (RightHigh - LeftHigh != root->_bf){cout << _root->_kv.first << "当前节点平衡因子有问题" << endl;return false;}return abs(LeftHigh- RightHigh) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}
三、实现AVL树的完整代码
AVLTree.h
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) //平衡因子起始值是0,当左子树插入一个节点时-1,右子树插入一个节点时+1{}
};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->_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);break;}else if (parent->_bf == -2 && cur->_bf == -1){//右单旋RotateR(parent);break;}else if (parent->_bf == 2 && cur->_bf == -1){//RL型RotateRL(parent);break;}else if (parent->_bf == -2 && cur->_bf == 1){//LR型RotateLR(parent);break;}}else{assert(false);}}return true;}void RotateL(Node* parent) //左单旋(RR型){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;parent->_right = subRL;subR->_left = parent;if(subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}parent->_parent = subR;parent->_bf = 0;subR->_bf = 0;}void RotateR(Node* parent) //右单旋(LL型){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentParent = parent->_parent;parent->_left = subLR;subL->_right = parent;if (subLR){subLR->_parent = parent;}if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}parent->_parent = subL;subL->_bf = parent->_bf = 0;}void RotateRL(Node* parent) //先右单旋,再左单旋(RL型){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){//subRL自己就是新增点parent->_bf = subR->_bf = 0;}else if (bf == -1){//subRL的左子树上新增parent->_bf = 0;subRL->_bf = 0;subR->_bf = 1;}else if (bf == 1){//subRL的右子树上新增parent->_bf = -1;subRL->_bf = 0;subR->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent) //先左单旋,再右单旋(LR型){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){//subLR自己就是新增点parent->_bf = subL->_bf = 0;}else if (bf == -1){//subLR的左子树上新增parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0;}else if (bf == 1){//subLR的右子树上新增parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else{assert(false);}}//中序打印void Inorder(){_Inorder(_root);cout << endl;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}//检查是否为AVL树bool IsBalance(){return _IsBalance(_root);}int _High(Node* root){if (root == nullptr)return 0;int LeftHigh = _High(root->_left);int RightHigh = _High(root->_right);return LeftHigh > RightHigh ? LeftHigh + 1 : RightHigh + 1;}bool _IsBalance(Node* root){if (root == nullptr)return true;int LeftHigh = _High(root->_left);int RightHigh = _High(root->_right);if (RightHigh - LeftHigh != root->_bf){cout << _root->_kv.first << "当前节点平衡因子有问题" << endl;return false;}return abs(LeftHigh- RightHigh) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}
private:Node* _root = nullptr;
};
test.c
//AVL树
#include"AVLTree.h"
int main()
{int a[] = { 16,3,7,11,9,26,18,14,15 }; AVLTree<int, int> t;for (auto e : a){t.Insert(make_pair(e, e));}t.Inorder();cout << "输出1代表是AVL树,输出0代表不是:" << t.IsBalance() << endl;return 0;
}
运行结果:
四、总结
AVL树的理解和实现总体来说还是比较难的,思路一定要搞清楚,代码实现上尽力而为
感谢各位大佬观看,创作不易,还请各位大佬点一个小小的赞!!!
相关文章:

【C++进阶学习】第七弹——AVL树——树形结构存储数据的经典模块
二叉搜索树:【C进阶学习】第五弹——二叉搜索树——二叉树进阶及set和map的铺垫-CSDN博客 目录 一、AVL树的概念 二、AVL树的原理与实现 AVL树的节点 AVL树的插入 AVL树的旋转 AVL树的打印 AVL树的检查 三、实现AVL树的完整代码 四、总结 前言:…...

px,em,rem之间的关系换算
px,em,rem之间的换算 px:普通大小 em:相对单位,相对于父元素的字体大小 rem:相对单位,相对于根元素(html)的字体大小 <!DOCTYPE html> <html lang"en"> <head>…...
HTTP——POST请求详情
POST请求 【传输实体文本】向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在POST请求体中。POST 请求可能会导致新的资源的建立或已有资源的修改。 场景: 1. 提交用户注册信息。 2. 提交修改的用户信息。 常见的…...

外包干了1个月,技术明显退步。。。
有一种打工人的羡慕,叫做“大厂”。 真是年少不知大厂香,错把青春插稻秧。 但是,在深圳有一群比大厂员工更庞大的群体,他们顶着大厂的“名”,做着大厂的工作,还可以享受大厂的伙食,却没有大厂…...

LeetCode加油站(贪心算法/暴力,分析其时间和空间复杂度)
题目描述 一.原本暴力算法 最初的想法是:先比较gas数组和cost数组的大小,找到可以作为起始点的站点(因为如果你起始点的油还不能到达下一个站点,就不能作为起始点)。当找到过后,再去依次顺序跑一圈,如果剩余的油为负数…...

5.1 软件工程基础知识-软件工程概述
软件工程诞生原因 软件工程基本原理(容易被考到) 软件生存周期 能力成熟度模型 - CMM 能力成熟度模型 - CMMI 真题...
HttpUtil工具
http工具 用到的依赖 <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version></dependency><dependency><groupId>org.apache.httpcomponent…...
并发编程-锁的分类
锁的分类 可重入锁&不可重入锁 可重入:当一个线程获取某个锁后,再次获取这个锁的时候是可以直接拿到的。不可重入:当一个线程获取某个锁之后,再次获取这个锁的时候拿不到,必须等自己先释放锁再获取。synchronized…...

K8S系列-Kubernetes基本概念及Pod、Deployment、Service的使用
一、Kubernetes 的基本概念和术语 一、资源对象 Kubernetes 的基本概念和术语大多是围绕资源对象 Resource Object 来说的,而资源对象在总体上可分为以下两类: 1、某种资源的对象 例如节点 Node) Pod 服务 (Service) 、存储卷 (Volume)。 2、…...

在VSCode上创建Vue项目详细教程
1.前期环境准备 搭建Vue项目使用的是Vue-cli 脚手架。前期环境需要准备Node.js环境,就像Java开发要依赖JDK环境一样。 1.1 Node.js环境配置 1)具体安装步骤操作即可: npm 安装教程_如何安装npm-CSDN博客文章浏览阅读836次。本文主要在Win…...
Go语言入门之流程控制简述
Go语言入门之流程控制简述 1.if语句 if语句和其他语言一样,只不过go语言的if不需要用括号包裹 if 语句的分支代码块的左大括号与 if 关键字在同一行上,这是 go 代码风格的统一要求 简单实例: func main() {// 猜数字a : 2if a > 0 {if a…...

接口测试框架基于模板自动生成测试用例!
引言 在接口自动化测试中,生成高质量、易维护的测试用例是一个重要挑战。基于模板自动生成测试用例,可以有效减少手工编写测试用例的工作量,提高测试的效率和准确性。 自动生成测试用例的原理 为了实现测试用例数据和测试用例代码的解耦&a…...
C++ STL stable_sort用法
一:功能 对区间内元素进行排序,保证相等元素的顺序(稳定排序) 二:用法 #include <iostream>struct Record {std::string label;int rank; };int main() {std::vector<Record> data {{"q", 1},…...
YOLO v8进行目标检测的遇到的bug小结
OSError: [WinError 1455] 页面文件太小,无法完成操作。 我的python环境是放在C盘的: 在“我的电脑”点击鼠标右键,打开“属性”点击高级系统设置点击“设置”找到“高级”点击“更改”分配“虚拟内存”(这里需要重启电脑才能生…...
FastAPI -- 第二弹(响应模型、状态码、路由APIRouter、后台任务BackgroundTasks)
响应模型 添加响应模型 from typing import Anyfrom fastapi import FastAPI from pydantic import BaseModel, EmailStrapp FastAPI()class UserIn(BaseModel):username: strpassword: stremail: EmailStrfull_name: str | None Noneclass UserOut(BaseModel):username: s…...

案例 | 人大金仓助力山西政务服务核心业务系统实现全栈国产化升级改造
近日,人大金仓支撑山西涉企政策服务平台、政务服务热线联动平台、政务网、办件中心等近30个政务核心系统完成全栈国产化升级改造,推进全省通办、跨省通办、综合业务受理、智能审批、一件事一次办等业务的数字化办结进程,为我国数字政务服务提…...

如何用python写接口
如何用python写接口?具体步骤如下: 1、实例化server 2、装饰器下面的函数变为一个接口 3、启动服务 开发工具和流程: python库:flask 》实例化server:server flask.Flask(__name__) 》server.route(/index,met…...

轻量级可扩展易航网址引导系统源码V2.45
由于现在网站行业的不稳定,导致很地址频繁更换,不仅是网站,联系QQ,加群链接等需要更换时,好不容易发展的客户会因为找不到您新的网站地址而流失,有了引导页以后就可以安心地宣传无需担心客户丢失的问题。 …...

解决ESLint和Prettier冲突的问题
在配置了ESLint的项目中使用Prettier进行格式化可能会出现冲突,不如Prettier配置了使用双引号,ESLint配置了单引号,当然可以一个一个改成一样的配置,但是比较麻烦。我发现可以直接使用ESLint的规则进行格式化。在VSCode配置过程如…...

C判断一个点在三角形上
背景 鼠标操作时,经常要判断是否命中显示控件,特开发此算法快速判断。 原理 三角形三等分点定理是指在任意三角形ABC中,可以找到三个点D、E和F,使得线段AD、BE和CF均等分三角形ABC。 这意味着三个等分点分别位于三个边界上&…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...