【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。 这意味着三个等分点分别位于三个边界上&…...

物业系统自主研发接口测试框架
1、自主研发框架整体设计 1.1、什么是测试框架? 在了解什么是自动化测试框架之前,先了解一下什么叫框架?框架是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架…...

手机和电脑通过TCP传输
一.工具 手机端:网络调试精灵 电脑端:野火网络调试助手 在开始通信之前,千万要查看一下电脑的防火墙是否关闭,否则可能会无法通信 在开始通信之前,千万要查看一下电脑的防火墙是否关闭,否则可能会无法通信…...

Git 在commit后,撤销commit
1. 撤销已经add,但是没有commit的问题 git reset HEAD 2. 撤销已经commit,但是没有push到远端的文件(仅撤销commit 保留add操作) 撤销上一次的提交 git reset --soft HEAD^windows 系统使用提示 more,需要多加一个…...

多模态大模型 - MM1
1. 摘要 本文主要通过分析模型结构和数据选择讨论如何构建一个好的多模态大模型(MLLM),并同时提出了MM1模型,包括30B dense版本和64B的MoE版本。 具体贡献: 模型层面:影响效果的重要性排序为:…...

FPGA设计之跨时钟域(CDC)设计篇(2)----如何科学地设计复位信号?
1、复位是干嘛的? 时钟信号和复位信号应该是一个数字系统最重要和最常用的两个信号了。时钟的重要性大家都懂,没有时钟整个系统就无法同步,自然也就谈不上运行了。那么复位(reset)到底是干嘛的? 所有的数字系统在上电的时候都会进行复位,这样才能确保该系统的初始运行状…...

GPS北斗标准时钟同步服务器结构是什么?安徽京准
GPS北斗标准时钟同步服务器结构是什么?安徽京准 GPS北斗标准时钟同步服务器结构是什么?安徽京准 电厂时钟同步系统组成及配置 随着计算机和网络通信技术的飞速发展,火电厂热工自动化系统数字化、网络化的时代已经到来。一方面它为控制和信息系…...

9.5 栅格图层符号化多波段彩色渲染
文章目录 前言多波段彩色渲染QGis设置为多波段彩色二次开发代码实现多波段彩色 总结 前言 介绍栅格图层数据渲染之多波段彩色渲染说明:文章中的示例代码均来自开源项目qgis_cpp_api_apps 多波段彩色渲染 以“3420C_2010_327_RGB_LATLNG.tif”数据为例,…...

力扣第九题
回文数 提示: 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 代码展示&#…...

鞭炮插画:成都亚恒丰创教育科技有限公司
鞭炮插画:年味里的绚烂记忆 在岁末年初的温柔时光里,总有一抹色彩,能瞬间唤醒沉睡的年味——那便是鞭炮插画中跃动的红与金,成都亚恒丰创教育科技有限公司 它们不仅仅是纸与墨的交织,更是情感与记忆的桥梁,…...

python 循环
循环 while语句 for语句 循环控制语句 break 立即退出循环。 continue 跳过当前循环的剩余部分,并开始下一次迭代。 else for 和 while 循环都可以有一个可选的 else 子句,当循环正常结束时执行。 嵌套 占位符pass pass 是一个空操作语句。当你需要在代…...