C++_AVL树

       

目录

1、AVL的概念

2、平衡因子的调整概念

3、AVL树的插入

3.1 调整平衡因子代码实现

3.2 右旋操作

3.2 左旋操作 

3.3 双旋-先右旋再左旋

3.4 双旋-先左旋再右旋

3.5 旋转操作的小结

4、AVL的验证与实现

结语


前言:

        在C++中,AVL树是在二叉搜索树的基础上优化而来的,因为二叉搜索树有一个缺陷,即如果插入的数据接近有序或者有序,那么二叉搜索树就会变成一边倒的结构,也就是”单支树“,而”单支树“查找数据效率就会变成和线性数据结构一样的O(N),失去了二叉树本有的优势。 单支树示意图如下:

        因此两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis1962推出了AVL树的概念,AVL树在每一次插入新节点的同时,会自动调整该树的高度,即不会让每个节点的左右子树高度的绝对值不超过1,因此哪怕面对有序数据的插入也不会让树变成”单支树“,可以使查找数据的效率始终保持在O(log N)。

1、AVL的概念

        AVL树满足以下两个条件:

        1、其每颗子树都是AVL树。

        2、每个节点的左右子树高度(可在节点中新加一个变量-平衡因子表示该节点的左右子树高度)的绝对值不超过1。

        AVL树示意图如下:

        其中,-1表示该节点的左子树高度高于右子树高度1个单位,0表示该节点左右子树高度一样,1表示该节点的右子树高度高于左子树高度1个单位。转换成公式:平衡因子=右子树高度-左子树高度

2、平衡因子的调整概念

        AVL树插入新节点的规则依旧是按照二叉搜索树的规则,即左节点小于根结点,而右节点大于跟节点。只不过AVL新加入了平衡因子的概念,所以每次插入节点后,都需要对平衡因子做出调整,比如插入的节点为该子树的根结点的右边,则根结点的平衡因子需要+1。

        此时,平衡因子的状态就会出现三种情况:

        1、插入节点后,根结点的平衡因子从正负1变为0,这种情况说明插入该节点后这棵树子树得到了平衡,无需做任何处理。

        2、插入节点后,根结点的平衡因子从0变为1或-1,这种情况说明插入的节点破坏了该树的原有平衡,虽然当前子树的根结点的平衡因子的绝对值没有超过1,但是该子树的祖先节点的平衡因子的绝对值可能已经超过1了,所以需要”向上更新“,更改祖先节点的平衡因子。

        3、插入节点后,该树的某个节点的平衡因子的绝对值超过了1,则需要对该子树进行旋转处理。

        具体示意图如下:

        此时会发现,不管新插入的节点在9的右边还是左边,都会导致节点8的平衡因子变成2,因为对于9来说可能在哪边插入节点都行,但是对于节点8来说,这两个插入的节点都在8的右子树,所以会导致8的平衡因子变成2。

        在节点9的左边插入节点的情况如下:


         只有在8的左边插入节点,才不会引发旋转调整,示意图如下:

3、AVL树的插入

        AVL树的插入函数的实现可以分成三步:1、找到插入节点的合适位置。2、调整节点的平衡因子。3、对平衡因子异常的节点进行旋转操作。

        第一步是复用了二叉搜索树的插入逻辑,即判断要插入节点的值是否大于或小于根结点,若大于根结点则往右子树遍历,若小于根结点则往左子树遍历,直到找到节点指向的nullptr处,然后将插入节点与其父母节点相连接。

        第二步调整平衡因子逻辑即上文叙述。

        第三步进行的旋转操作有:左旋、右旋、双旋转(即左右旋的结合),具体如下文。

3.1 调整平衡因子代码实现

        将上述的调整场景转换为代码:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;template<class K, class V>
class AVLTreeNode//节点
{AVLTreeNode<K, V>* _left;//指向左节点AVLTreeNode<K, V>* _right;//指向右节点AVLTreeNode<K, V>* _parent;//指向自己的父母节点pair<K, V> _kv;// 记录数据用的pair类型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//AVL树
{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;//AVL和二叉搜索树一样不允许有相同数据}}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)//等于0说明平衡了,则不处理{break;}else if (parent->_bf == 2 || parent->_bf == -2){// 需要旋转处理 -- 1、让这颗子树平衡 2、降低这颗子树的高度}else{assert(false);}}return true;}private:Node* _root = nullptr;
};

        以上代码已经实现了合适位置的查找和平衡因子的调整,插入函数的实现还差最后一步,即平衡因子等于2或者-2时旋转操作的实现。

3.2 右旋操作

        右旋操作是针对平衡因子为-2的场景,说明该节点的左子树比右子树要高,需要右旋降低左子树的高度。并且旋转完成后要更新平衡因子,具体操作示意图如下:

        从结果可以看到,原本平衡因子是-2的节点经过旋转操作之后变成了0。并且旋转之后的节点与节点之间的逻辑依然是遵循小于根结点的在左边,大于根节点的在右边。

3.2 左旋操作 

        左旋操作是针对平衡因子为2的场景,说明该节点的右子树比左子树要高,需要左旋降低右子树的高度。并且旋转完成后要更新平衡因子,具体操作示意图如下:

         从结果可以看到,原本平衡因子是2的节点经过旋转操作之后变成了0。

        无论是左旋还是右旋,都要注意两个事项:

        一、拿上述左旋举例,节点12不一定存在左孩子,如果不存在左孩子则10的平衡因子是-1。

        二、节点10不一定是根结点,可能是某个节点的左子树或者右子树,若10只是子树,则旋转后要将节点12与原先节点10的父母节点相连接。

3.3 双旋-先右旋再左旋

        从上述的例子可以发现,插入的节点都处于”边缘“节点下,比如拿上述的左旋举例,若插入的节点是插入到节点11下则如何调整呢?这时候就要用到双选调整,比如上述的节点14作为节点11的孩子插入,则需要以节点12为一棵子树,先进行右旋操作,然后再以根结点10进行左旋操作。

        先右旋再左旋示意图如下:

3.4 双旋-先左旋再右旋

         用上述右旋的例子来进行说明,如果插入的节点是作为节点8的孩子节点,则需要先以节点6为子树进行左旋,然后再以根结点10进行整棵树的右旋。

        先左旋再右旋的示意图如下:

3.5 旋转操作的小结

经过上述旋转操作的细述,大致可以得出以下结论:

一、平衡因子为2的节点,说明该节点的右子树高,因此只需要关注该节点的右孩子,其右孩子的平衡因子会有两种情况:

        1、该节点平衡因子若为1则只需要进行左旋转即可。

        2、该节点平衡因子若为-1则需要进行双旋-先右旋再左旋。

二、平衡因子为-2的节点,说明该节点的左子树高,因此只需要关注该节点的左孩子,其左孩子的平衡因子会有两种情况:

        1、该节点平衡因子若为1则需要进行双旋-先左旋再右旋。

        2、该节点平衡因子若为-1则只需要进行右旋转即可。

        从以上结论可以写出旋转代码。

4、AVL的验证与实现

        判断一棵树是否为AVL树的依据有两点:1、中序遍历的方式打印出来的是有序数据。2、每个节点的平衡因子的绝对值不超过2。

        AVL树的实现代码如下: 

#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <time.h>
#include<iostream>
using namespace std;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; // balance factorAVLTreeNode(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)//中序遍历AVL树{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}t1.InOrder();cout << t1.IsBalance() << endl;return 0;
}

结语

        以上就是关于AVL的讲解,AVL的重点在于对旋转的理解,理清旋转的逻辑与各个节点之间的逻辑关系尤为重要。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/715166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

旧的Spring Security OAuth已停止维护,全面拥抱新解决方案Spring SAS

Spring Authorization Server 替换 Shiro 指引 背景 Spring 团队正式宣布 Spring Security OAuth 停止维护&#xff0c;该项目将不会再进行任何的迭代 目前 Spring 生态中的 OAuth2 授权服务器是 Spring Authorization Server 已经可以正式生产使用作为 SpringBoot 3.0 的最新…

如何使用naive 做一个模态框的方式

1.我的问题使用了一个table 表格&#xff0c;在表格中设置俩个按钮 最后做出来的效果 <template><div><h1>测试文件</h1><!-- 表格 --><n-data-table :columns"columns" :data"data" :pagination"pagination" …

Linux内核队列queue.h

文章目录 一、简介二、SLIST单向无尾链表2.1 介绍2.2 操作2.3 例子 三、STAILQ单向有尾链表四、LIST双向无尾链表五、TAILQ双向有尾链表六、CIRCLEQ循环链表七、queue源码参考 一、简介 queue.h是一个非常经典的文件&#xff0c;定义了一系列宏的操作&#xff0c;它定义了一系…

笔记72:关于IMU(惯性测量单元)传感器的作用【不涉及公式推导】

一、IMU传感器是什么&#xff1a; 惯性测量单元IMU&#xff08;Inertial Measurement Unit&#xff09;是一种使用【加速度计】和【陀螺仪】来测量【物体三轴姿态角&#xff08;空间姿态&#xff09;】的装置&#xff1b;IMU在坐标系的每个坐标轴上&#xff0c;均安装有1个陀螺…

Vue2:用node+express部署Vue项目

一、编译项目 命令 npm run build执行命令后&#xff0c;我们会在项目文件夹中看到如下生成的文件 二、部署Vue项目 接上一篇&#xff0c;nodeexpress编写轻量级服务 1、在demo中创建static文件夹 2、将dist目录中的文件放入static中 3、修改server.js文件 关键配置&…

DFA还原白盒AES密钥

本期内容是关于某app模拟登录的,涉及的知识点比较多,有unidbg补环境及辅助还原算法,ida中的md5以及白盒aes,fart脱壳,frida反调试 本章所有样本及资料均上传到了123云盘 llb资料官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 目录 首先抓包 fart脱壳 加密位置定位…

win11安装nodejs

一、下载安装包 链接: https://pan.baidu.com/s/1_df8s1UlgNNaewWrWgI59A?pwdpsjm 提取码: psjm 二、安装步骤 1.双击安装包 2.Next> 3.勾选之后&#xff0c;Next> 4.点击Change&#xff0c;选择你要安装的路径&#xff0c;然后Next> 5.点击Install安装 二、…

学生云服务器腾讯云_腾讯云学生学生_腾讯云学生云主机

2024年腾讯云学生服务器优惠活动「云校园」&#xff0c;学生服务器优惠价格&#xff1a;轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年&#xff0c;轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&#xff0c;CVM云服务器2核4G配置842.4元一年&…

基于扩散模型的图像编辑:首篇综述

AIGC 大模型最火热的任务之一——基于 Diffusion Model 的图像编辑(editing)领域的首篇综述。长达 26 页&#xff0c;涵盖 297 篇文献&#xff01;本文全面研究图像编辑前沿方法&#xff0c;并根据技术路线精炼地划分为 3 个大类、14 个子类&#xff0c;通过表格列明每个方法的…

LeetCode 热题 100 | 图论(一)

目录 1 200. 岛屿数量 2 994. 腐烂的橘子 2.1 智障遍历法 2.2 仿层序遍历法 菜鸟做题&#xff0c;语言是 C 1 200. 岛屿数量 解题思路&#xff1a; 遍历二维数组&#xff0c;寻找 “1”&#xff08;若找到则岛屿数量 1&#xff09;寻找与当前 “1” 直接或间接连接在…

Java输入输出流详细解析

Java I/O&#xff08;输入/输出&#xff09;主要被用来处理输入数据和输出结果。 在Java中&#xff0c;输入/输出操作被当作流&#xff08;Stream&#xff09;进行处理。流是一个连续的数据流入或数据流出的通道。流操作在Java中主要可以分为两种类型&#xff1a;字节流和字符…

基于ssm疫情期间高校防控系统+vue论文

摘 要 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;学生信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行处理不能满足广大…

‘conda‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件

如果你在运行 conda 命令时收到了 ‘conda’ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 的错误消息&#xff0c;这可能意味着 Anaconda 并没有正确地添加到你的系统路径中。 1.你可以尝试手动添加 Anaconda 到系统路径中。以下是在 Windows 系统上添加…

19.2 DeepMetricFi:基于深度度量学习改进Wi-Fi指纹定位

P. Chen and S. Zhang, "DeepMetricFi: Improving Wi-Fi Fingerprinting Localization by Deep Metric Learning," in IEEE Internet of Things Journal, vol. 11, no. 4, pp. 6961-6971, 15 Feb.15, 2024, doi: 10.1109/JIOT.2023.3315289. 摘要 Wi-Fi RSSI指纹定位…

调用“每日诗词”在你的页面添加一句诗

概述 前几天浏览网站的时候看到页面上有句诗&#xff0c;打开调试看了下调用的是“每日诗词”的SDK。本文基于此SDK实现你的页面添加一句诗。 实现效果 实现 1. 引入SDK <script src"https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset"utf-…

mysql服务治理

一、性能监控指标和解决方案 1.QPS 一台 MySQL 数据库&#xff0c;大致处理能力的极限是&#xff0c;每秒一万条左右的简单 SQL&#xff0c;这里的“简单 SQL”&#xff0c;指的是类似于主键查询这种不需要遍历很多条记录的 SQL。 根据服务器的配置高低&#xff0c;可能低端…

【BUUCTF web】通关 2.0

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

2024年2月国内如何快速注册OnlyFans最新小白教学

前言 onlyface软件是一个创立于2016年的订阅式社交媒体平台&#xff0c;创作者可以在自己的账号发布原创的照片或视频&#xff0c;并将其设置成付费模式&#xff0c;若用户想查看则需要每月交费订阅。 需要注意的是&#xff0c;网络上可能存在非法或不道德的应用程序&#xff…

获取当前数据 上下移动

点击按钮 上下移动 当前数据 代码 // 出国境管理 登记备案人员列表 <template><a-row><a-col span"24"><a-card :class"style[a-table-wrapper]"><!-- 出国境 登记备案人员列表 --><a-table:rowKey"records >…

淘宝开放平台获取商家订单数据API接口接入流程

taobao.custom 自定义API操作 接口概述&#xff1a;通过此API可以调用淘宝开放平台的API&#xff0c;通过技术对接&#xff0c;您可以轻松实现无账号调用官方接口。进入测试&#xff01; 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&…