C++数据结构——AVL树

前言:本篇文章将紧随二叉搜索树的节奏,分享一个新的数据结构——AVL树。


目录

一.AVL树概念

二.AVL树插入规则

三.AVL树实现

1.基本框架

2.插入

3.旋转

1)左\右单旋

2)左右/右左双旋

4.遍历

5.求树高度

6.判断平衡

7.求树高度

总结


一.AVL树概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

但是当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度

所以AVL树即:一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)


二.AVL树插入规则

由于AVL树的独特结构,我们给出以下的插入规则: 

1.按照搜索树规则插入。

2.更新插入节点的祖先节点的平衡因子:

        a.插入父亲的左边,父亲的平衡因子--

        b.插入父亲的右边,父亲的平衡因子++

        c.父亲的平衡因子 == 0,父亲所在的子树高度不变,不再往上更新,插入结束。

        d.父亲平衡因子 == 1 or -1,父亲所在的子树高度变了,往上更新,重复以上步骤。

        e.父亲平衡因子 == 2 or -2,父亲所在的子树已经不平衡了,需要旋转处理


三.AVL树实现

1.基本框架

template<class K,class V>
struct AVLTreeNode
{struct AVLTreeNode<K,V>* _left;struct AVLTreeNode<K,V>* _right;struct AVLTreeNode<K,V>* _parent;int _bf;pair<K, V> _kv;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;
private:Node* _root;
};

基本框架与平衡二叉树类似,区别在于AVL树的节点为键值对

同时我们还需增加平衡因子_bf和父节点_parent,方便我们进行调整。


2.插入

	//插入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 (kv.first < parent->_kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//更新平衡因子return true;}

基本的插入步骤与平衡二叉树一模一样,需要关注的就是插入的节点变为键值对

下面我们单独来看如何更新平衡因子

        while (parent){if (cur == cur->_parent->_left)parent->_bf--;elseparent->_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){//出现问题,进行旋转break;}elseassert(false);}

按照我们上边的规则其实很好写出上述代码,要注意循环条件为parent如果没有父亲,也就是到达了根节点,那就无法再进行调整。 

下面我们来重点关注,如何进行旋转


3.旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:

  1.  新节点插入较高左子树的左侧---左左:右单旋。
  2.  新节点插入较高右子树的右侧---右右:左单旋。
  3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋。
  4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋。

下面我们就一一来看这四种情况。


1)左\右单旋

先来看右单旋,可以抽象理解为,左子树过高,需要向右边旋转拉低。 

从上图能够看出,右单旋的步骤为:

  1. 让平衡因子为-2的节点成为它的左子节点的右子节点;
  2. 同时让该左子节点的右子节点成为平衡因子为-2的节点的左子节点。

同时我们需要关注的细节是:

  • 平衡因子为-2的节点是否为根节点。如果不是根节点则需要调整其父节点的指向。
  • 左子节点的右子节点是否为空。

通过这样的调整,就可以实现平衡,同时调整的两个关键节点的平衡因子均归0。 

下面来看代码:

	void RotateR(Node* parent){//定义左子节点Node* subL = parent->_left;//定义左子节点的右子节点Node* subLR = subL->_right;//调整parent->_left = subLR;//判空if (subLR)subLR->_parent = parent;//调整subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root)//判断是否为根{_root = subL;_root->_parent = nullptr;}else//不是根节点,调整父节点指向{if (ppNode->_left == parent)ppNode->_left = subL;elseppNode->_right = subL;subL->_parent = ppNode;}//平衡因子归0parent->_bf = subL->_bf = 0;}

再来看左单旋: 

 左单旋则与右单旋完全相反,所以我们不做过多解释,直接给出代码:

	//左单旋void RotateL(Node* parent){//定义右子节点Node* subR = parent->_right;//定义右子节点的左子节点Node* subRL = subR->_left;//调整parent->_right = subRL;//判空if (subRL)subRL->_parent = parent;//调整subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root)//判断是否为根{_root = subR;_root->_parent = nullptr;}else//不是根节点,调整父节点指向{if (ppNode->_left == parent)ppNode->_left = subR;elseppNode->_right = subR;subR->_parent = ppNode;}//平衡因子归0parent->_bf = subR->_bf = 0;}

2)左右/右左双旋

如果说树并不是子树的一条斜边独高,而是折线型的一颗子树高,此时单靠单旋是解决不了问题的,因此需要通过双旋来解决

上图所示为先左后右的折线型,所以我们需要进行左右双旋,步骤为:

  1. 先从折线的折点位置,即上图的30位置,进行左单旋,使树变为左边一条斜边独高的树。
  2. 在从折线起点位置进行右单旋。
  3. 更新平衡因子。

其中更新平衡因子也分为不同的情况,以上图为例:

  • 如果新节点插入位置为60的左,那么旋转后60为0,30为0,90为1。
  • 如果新节点插入位置为60的右,那么旋转后60为0,30为-1,90为0。
  • 如果新节点就是60,那么三者的平衡因子均为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){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 1;}else if (bf == 1){subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}

 注意更新平衡因子是通过初始时折线末点的平衡因子判断的,所以要提前记录。


再来看右左双旋

 与左右双旋相反,右左双旋是先右后左的折线,所以其操作步骤与之相反:

  1. 先从折线的折点位置,即上图的90位置,进行右单旋,使树变为右边一条斜边独高的树。
  2. 在从折线起点位置进行左单旋。
  3. 更新平衡因子。

其中更新平衡因子也同样分为不同的情况,以上图为例:

  • 如果新节点插入位置为60的左,那么旋转后60为0,30为0,90为1。
  • 如果新节点插入位置为60的右,那么旋转后60为0,30为-1,90为0。
  • 如果新节点就是60,那么三者的平衡因子均为0。

代码如下:

	//右左双旋void RotateLR(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(parent->_right);RotateL(parent);if (bf == -1){subR->_bf = 1;subRL->_bf = 0;parent->_bf = 0;}else if (bf == 1){subR->_bf = 0;subRL->_bf = 0;parent->_bf = -1;}else if (bf == 0){subL->_bf = 0;subLR->_bf = 0;parent->_bf = 0;}else{assert(false);}}

根据父节点及其左右子节点的平衡因子,即可判断对应的旋转方式,下面补充插入步骤:

 

			else if (parent->_bf == 2 || parent->_bf == -2){//出现问题,进行旋转//左单旋if (parent->_bf == -2 && parent->_left->_bf == -1){RotateL(parent);}//右单旋else if (parent->_bf == 2 && parent->_right->_bf == 1){RotateR(parent);}//左右单旋else if (parent->_bf == -2 && parent->_left->_bf == 1){RotateLR(parent);}//右左单旋else{RotateRL(parent);}break;}

4.遍历

遍历操作与二叉搜索树类似,需要修改的是我们需要将键值对均打印出来:

	//遍历void InOrder(){inOrder(_root);cout << endl;}void inOrder(const Node* root){if (root == nullptr){return;}inOrder(root->_left);cout << root->_kv.first << ':' << root->_kv.second << " ";inOrder(root->_right);}

为了方便调用函数而无需传参,我们采用如上方式进行代码编写。 


5.求树高度

求树高度我们前边在讲解二叉树的时候已经分享过了,只需求出左右子树高度的最大值+1即可,通过递归计算:

	//求树高度int Height(const Node* root){if (root == nullptr)return 0;return max(Height(root->_left), Height(root->_right)) + 1;}

6.判断平衡

判断树是否平衡,即判断两棵子树的高度差是否大于等于2

	//判断平衡bool IsBalance(){return isBalance(_root);}bool isBalance(const Node* root){if (root == nullptr)return true;int leftHeight = Height(root->_left);int rightHeight = Height(root->_right);if (abs(leftHeight - rightHeight) >= 2)return false;//检查平衡因子if (rightHeight - leftHeight != root->_bf)return false;return isBalance(root->_left) && isBalance(root->_right);}

同时还需要通过递归来判断各个子树是否平衡


7.求树高度

求树的大小,通过递归即求左子树的大小+右子树的大小+根节点:

	//求树大小int Size(){return size(_root);}int size(const Node* root){if (root == nullptr)return 0;return size(root->_left) + size(root->_right) + 1;}

总结

关于AVL树的基本内容就分享这么多,喜欢本篇文章的小伙伴记得一键三连,我们下期再见!

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

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

相关文章

仿真算法验证成功后,如何快速实现真机无缝切换?

Prometheus仿真优势 首先&#xff0c;我们先通过下面这个视频了解一下Prometheus仿真有哪些优势&#xff1a; 开源自主无人机平台重大更新&#xff01;Promethus仿真到真机无缝切换 Prometheus仿真最大的优势之一是采用了模块化设计&#xff0c;对每个操作节点进行了封装&…

影刀进行shopee商品排名零代码爬取

需要研究shopee平台的排名更新时间段和周期&#xff0c;几分钟用影刀写了一个爬取应用&#xff0c;每10分钟进行一次排名爬取&#xff08;以fan‘风扇’为例&#xff09;&#xff0c;0代码爬取。 打开’fan’关键词搜索网页&#xff1b;等待网页加载&#xff1b;滚动进一步加载…

如何使用Python为Excel文件添加预设文档属性和自定义文档属性

向Excel文件添加文档属性是专业地组织和管理电子表格数据的关键步骤。这些属性&#xff0c;如标题、作者、主题和关键词&#xff0c;增强了文件的元数据&#xff0c;使得在大型数据库或文件系统中跟踪、排序和搜索文档变得更加容易。通过包含这些信息&#xff0c;您不仅提高了文…

太牛了!360大佬编写的《应急响应指导手册》火了!(PDF限时3天领取)

免责声明&#xff1a; 请使用者遵守《中华人民共和国网络安全法》&#xff0c;由于传播、利用本账号所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;公众号及作者不为此承担任何责任。 简介 这份《应急响应指导手册》&#xf…

CSS跳动文字

<div class"loading-mask"><div class"loading-text"><span style"--i:1">加</span><span style"--i:2">载</span><span style"--i:3">中</span><span style"--i:…

android进阶-AIDL

参考&#xff1a;Android进阶——AIDL详解_android aidl-CSDN博客 AIDL&#xff08;Android 接口定义语言&#xff09;&#xff0c;可以使用它定义客户端与服务端进程间通信&#xff08;IPC&#xff09;的编程接口&#xff0c;在 Android 中&#xff0c;进程之间无法共享内存&…

Word设置代码块格式

前言 Word中无法像Markdown和LaTeX一样插入代码块&#xff0c;若要在Word中插入代码块可以手动设置代码块格式或自动粘贴代码块格式。若不追求完美高亮效果&#xff0c;可使用前者方案&#xff1b;若追求完美的高亮效果&#xff0c;可使用后者方案。下文介绍这2种方案。 手动…

C++ 多态的相关问题

目录 1. 第一题 2. 第二题 3. inline 函数可以是虚函数吗 4. 静态成员函数可以是虚函数吗 5. 构造函数可以是虚函数吗 6. 析构函数可以是虚函数吗 7. 拷贝构造和赋值运算符重载可以是虚函数吗 8. 对象访问普通函数快还是访问虚函数快 9. 虚函数表是什么阶段生成的&…

华为 Huawei 交换机 配置 Dot1q 终结子接口实现同设备 VLAN 间通信示例

组网需求 企业的不同部门拥有相同的业务&#xff0c;如上网、 VoIP 等业务&#xff0c;且各个部门中的用户位于不同的网段。目前存在不同的部门中相同的业务所属的VLAN 不相同&#xff0c;现需要实现不同VLAN中的用户相互通信。 如 图 7-7 所示&#xff0c;部门 1 和部门 2 中…

【拼多多笔试题汇总】2024-05-09-拼多多春招笔试题-三语言题解(Cpp/Java/Python)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新拼多多近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

常见物联网面试题详解

物联网一直是非常火热的行业&#xff0c;G端如智慧城市、智慧工厂、智慧园区、智慧水利、智慧矿山等行业&#xff0c;都会涉及到物联网&#xff0c;基本都是软硬一体&#xff0c;因此当面试相关企业时&#xff0c;物联网平台是面试企业重点考察的项&#xff0c;小伙伴如果从事相…

前端使用Compressor.js实现图片压缩上传

前端使用Compressor.js实现图片压缩上传 Compressor.js官方文档 安装 npm install compressorjs使用 在使用ElementUI或者其他UI框架的上传组件时&#xff0c;都会有上传之前的钩子函数&#xff0c;在这个函数中可以拿到原始file&#xff0c;这里我用VantUI的上传做演示 a…

财务管理|基于SprinBoot+vue的财务管理系统(源码+数据库+文档)

财务管理系统 目录 基于SprinBootvue的财务管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 1管理员功能模块 2员工功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1…

Docker快速搭建NAS服务——NextCloud

Docker快速搭建NAS服务——NextCloud 文章目录 前言NextCloud的搭建docker-compose文件编写运行及访问 总结 前言 本文主要讲解如何使用docker在本地快速搭建NAS服务&#xff0c;这里主要写如下两种&#xff1a; FileBrowser1&#xff1a;是一个开源的Web文件管理器&#xff…

idea运行项目报错提示:java: 错误: 不支持发行版本 19,让我来看看

在项目经常切换jdk时&#xff0c;这个error经常能遇到“不支持发行版本19”&#xff0c;这个问题修改起来其实很简单&#xff0c;但在真正操作到能够解决问题的那一步前&#xff0c;通常习惯先去查看配置的jdk版本是否是选择正确的&#xff0c;也就是先确认当前这个项目选择的j…

西湖大学英语听力考试音频无线发射系统-英语听力发射系统浅析

西湖大学英语听力考试音频无线发射系统-英语听力发射系统浅析 由北京海特伟业科技任洪卓发布于2024年5月10日 西湖大学&#xff0c;这所矗立于时代前沿的高等学府&#xff0c;始终秉持着创新精神和追求卓越的坚定信念&#xff0c;不断致力于教学质量的提升与学术研究的深化。其…

实体门店超-常规营销获客:218套落地方案/打造引流/锁客/复购/裂变营销

课程内容&#xff1a; 1 记住&#xff0c;生意不好不一定是你产品出了问题,mp4 2 生意人为什么要从产品思维向流量思维转型&#xff0c;社区超市每月多5万.mp4 3 实体老板不懂鱼塘理论只能等死&#xff0c;美业1招锁定275名年用户卡,mp4 4 餐饮赢销八部&#xff0c;帮你引爆…

MathType2024官方版数学公式编辑器功能全面介绍

在数字化学习和科研的浪潮中&#xff0c;数学公式的编辑与展示成为了不可或缺的一部分。MathType&#xff0c;作为一款专业的数学公式编辑器&#xff0c;凭借其强大的功能和便捷的操作&#xff0c;为科研人员、教师、学生等广大用户提供了极大的便利。下面&#xff0c;我们将对…

【Linux】模拟实现bash(简易版)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

视频号小店应该如何开店呢?详细的开店流程分享给你!

大家好&#xff0c;我是电商小V 视频号小店就是威信视频号团队为咱们商家提供的卖货平台&#xff0c;可以说是支持咱们商家在视频号场景中开店进行经营的模式&#xff0c; 视频号大概的开店流程那就是&#xff1a;找到视频号开店&#xff0c;选择企业入驻&#xff0c;填写信息&…