数据结构:AVL树的实现和全部图解

文章目录

  • 为什么要有AVL树
  • 什么是AVL树
  • AVL树的实现
    • 元素的插入
    • 平衡因子的更新
    • AVL树的旋转
  • AVL树的检查
  • 完整实现

本篇总结的是AVL树中的全部内容,配有详细的图解过程

为什么要有AVL树

前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现

什么是AVL树

当一个二叉搜索树形如下面的场景时,进行查找的时候效率会相当低下

在这里插入图片描述

甚至当接近为单支树的时候,查找效率会相当于在顺序表中的查找效率,因此俄罗斯的数学家G.M.Adelson-VelskiiE.M.Landis发明了一个解决方案:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度,这也就是AVL树的由来

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

  1. 它的左右子树都是AVL
  2. 左右子树高度之差的绝对值不超过1

左右子树的高度差也叫做平衡因子,在这样的树中进行搜索,时间复杂度是O(logN)

AVL树的实现

元素的插入

首先定义出节点,由于AVL树对于向上寻找信息有需求,因此在定义节点信息的时候要有该节点指向父亲的指针

// 定义AVL树的节点
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(val), bf(0){}// 左子树指针 右子树指针 父亲节点指针 节点值 平衡因子AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int bf;
};

下面定义AVL树的插入,AVL树的插入主要是先插入到一个二叉搜索树中,再对于二叉搜索树进行平衡,因此要先插入到二叉搜索树

bool Insert(const pair<K, V> kv)
{Node* newnode = new Node(kv);if (_root == nullptr){_root = newnode;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (newnode->_kv.first > cur->_kv.first){parent = cur;cur = cur->_left;}else if (newnode->_kv.first < cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}if (newnode->_kv.first > parent->_kv.first){parent->_right = newnode;cur->_parent = parent;}else{parent->_left = newnode;cur->_parent = parent;}
}

上面就是二叉搜索树的基本部分,下面开始的是AVL树平衡问题

平衡因子的更新

新节点插入后,AVL树的平衡性可能会遭到破坏,引入平衡因子的意义也就在于此,因此插入后的第一步是要更新平衡因子,看有没有遭到破坏,对于平衡因子的调整有下面的几种情况

  1. 如果插入到节点的左侧,平衡因子-1即可
  2. 如果插入到节点的右侧,平衡因子+1即可

节点插入后会有新的问题,看下面的场景

在这里插入图片描述

依据这个原理,可以写出代码来更新平衡因子

	// 节点的插入// 节点插入的逻辑和搜索树基本一致,只是如果不符合要求需要进行旋转bool Insert(const pair<K, V> kv){Node* newnode = new Node(kv);if (_root == nullptr){_root = newnode;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (newnode->_kv.first > cur->_kv.first){parent = cur;cur = cur->_left;}else if (newnode->_kv.first < cur->_kv.first){parent = cur;cur = cur->_right;}else{return false;}}if (newnode->_kv.first > parent->_kv.first){parent->_right = newnode;cur->_parent = parent;}else{parent->_left = newnode;cur->_parent = parent;}// 上面就是二叉搜索树的基本部分,下面更新平衡因子while (parent){// 如果cur是parent的左节点,平衡因子-1,如果是右节点,平衡因子+1if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}// 判断父节点平衡因子绝对值的情况:// 1. 如果为0,则证明不需要向上更新// 2. 如果为1,则需要向上更新// 3. 如果为2,则需要进行旋转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{// 如果绝对值比2大,说明平衡因子出错了,强制结束assert(false);}}}

AVL树的旋转

那当上面的情况已经发生,AVL树应该如何进行平衡?这就引入了旋转的概念

AVL树中,当破坏了AVL树的平衡后,总共有四种会引发的旋转

1. 新节点插入较高左子树的左侧,引发右单旋

在这里插入图片描述
因此下面实现右单旋的代码实现:

	// 右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}subL->_bf = parent->_bf = 0;}

2. 新节点插入在较高右子树的右侧,引发左单旋

在这里插入图片描述

	// 左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 更新节点parent->_right = subRL;parent->_parent = subR;subR->_left = parent;subR->_parent = parentParent;if (subRL)subRL->_parent = parent;//  更新parentParentif (_root == parent){_root = subR;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}}parent->_bf = subR->_bf = 0;}

3. 新节点插入较高右子树的左侧,引发先右单旋再左单旋

对于这种情况来说,总共有下面的两种情况

在这里插入图片描述
代码实现也有了思路,先左单旋再右单旋,最后修改平衡因子的值即可,而修改平衡因子的值,可以通过subRLbf值来判断不同的情况,根据不同的情况修改不同的值即可

	// 先右单旋再左单旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 0){// 自己就是新增的节点parent->_bf = subR->_bf = 0;}else if (bf == -1){// 在左子树进行的插入parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){// 在右子树进行的插入subRL->_bf = 0;subR->_bf = 0;parent->_bf = -1;}else{assert(false);}}

4. 新节点插入较高左子树的右侧,引发先左单旋再右单旋

分析方法和前面类似

	// 先进行左单旋,再进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);// 更换平衡因子if (bf == 0){subL->_bf = subLR->_bf = 0;}else if (bf == 1){// 插入在右子树subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){// 插入在左子树parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}

AVL树的检查

依据上面的实现可以基本实现AVL树,那如何验证AVL树是否正确?其实验证也很简单,只需要看每个节点的平衡因子是否等于对应的右子树减左子树的值即可

	// 用于检查树的高度int TreeHeight(Node* root){if (root == nullptr)return 0;int leftheight = TreeHeight(root->_left);int rightheight = TreeHeight(root->_right);return max(leftheight, rightheight) + 1;}// 保持树的封装 进行检查AVL树bool IsBalance(){return _IsBalance(root);}// 进行检查AVL树bool _IsBalance(Node* root){if (root == nullptr)return true;int leftheight = TreeHeight(root->_left);int rightheight = TreeHeight(root->_right);if (rightheight - leftheight != root->_bf)return false;return abs(rightheight-leftheight)<2 && _IsBalance(root->_left) && _IsBalance(root->_right);}

这样就完成了AVL树的测试

完整实现

#include <iostream>
#include <assert.h>
using namespace std;// 定义AVL树的节点
template<class K, class V>
struct AVLTreeNode
{AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}// 左子树指针 右子树指针 父亲节点指针 节点值 平衡因子AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf;
};// 定义AVL树
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree():_root(nullptr){}// 节点的插入// 节点插入的逻辑和搜索树基本一致,只是如果不符合要求需要进行旋转bool Insert(const pair<K, V> kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (kv.first > parent->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}// 上面就是二叉搜索树的基本部分,下面更新平衡因子while (parent){// 如果cur是parent的左节点,平衡因子-1,如果是右节点,平衡因子+1if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}// 判断父节点平衡因子绝对值的情况:// 1. 如果为0,则证明不需要向上更新// 2. 如果为1,则需要向上更新// 3. 如果为2,则需要进行旋转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){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{// 如果绝对值比2大,说明平衡因子出错了,强制结束assert(false);}}return true;}// 右单旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}subL->_bf = parent->_bf = 0;}// 左单旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentParent = parent->_parent;// 更新节点parent->_right = subRL;parent->_parent = subR;subR->_left = parent;subR->_parent = parentParent;if (subRL)subRL->_parent = parent;//  更新parentParentif (_root == parent){_root = subR;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}}parent->_bf = subR->_bf = 0;}// 先右单旋再左单旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;RotateR(subR);RotateL(parent);if (bf == 0){// 自己就是新增的节点parent->_bf = subR->_bf = 0;}else if (bf == -1){// 在左子树进行的插入parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else if (bf == 1){// 在右子树进行的插入subRL->_bf = 0;subR->_bf = 0;parent->_bf = -1;}else{assert(false);}}// 先进行左单旋,再进行右单旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);// 更换平衡因子if (bf == 0){subL->_bf = subLR->_bf = 0;}else if (bf == 1){// 插入在右子树subL->_bf = -1;subLR->_bf = 0;parent->_bf = 0;}else if (bf == -1){// 插入在左子树parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}// 用于检查树的高度int TreeHeight(Node* root){if (root == nullptr)return 0;int leftheight = TreeHeight(root->_left);int rightheight = TreeHeight(root->_right);return max(leftheight, rightheight) + 1;}// 保持树的封装 进行检查AVL树bool IsBalance(){return _IsBalance(root);}// 进行检查AVL树bool _IsBalance(Node* root){if (root == nullptr)return true;int leftheight = TreeHeight(root->_left);int rightheight = TreeHeight(root->_right);if (rightheight - leftheight != root->_bf)return false;return abs(rightheight-leftheight)<2 && _IsBalance(root->_left) && _IsBalance(root->_right);}private:Node* _root;
};

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

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

相关文章

白盒测试用例的设计(图文讲解,超详细)

正文 语句覆盖&#xff1a;每条语句至少执行一次。判定覆盖&#xff1a;每个判定的所有可能结果至少出现一次。&#xff08;又称“分支覆盖”&#xff09;条件覆盖&#xff1a;每个条件的所有可能结果至少执行一次。判定/条件覆盖&#xff1a;一个判定中的每个条件的所有可能结…

开源七轴myArm协作机械臂正逆运动学技术讲解

引言&#xff1a; 在本文中&#xff0c;我们将深入探讨机器人学的两个核心概念&#xff1a;正运动学和逆运动学。这两个概念是理解和控制机械臂运动的基础。通过一个具体的7轴机械臂实例&#xff0c;我们将详细介绍如何计算机械臂的正运动学和逆运动学。我们首先会解释正运动学…

uniapp 微信小程序 uni-file-picker上传图片报错 chooseAndUploadFile

这个问题真的很搞&#xff0c; 原因是微信开发者工具更新了&#xff0c;导致图片上传问题。 解决方法&#xff1a; 将微信开发者工具的基础库改为2.33.0一下即可。 在微信开发者工具详情 - 本地设置中&#xff08;记得点击‘推送’按钮&#xff09;&#xff1a;

【内功修炼】详解函数栈帧的创建和销毁

文章目录 1. 什么是函数栈帧2. 理解函数栈帧能解决什么问题呢&#xff1f;3. 函数栈帧的创建和销毁解析3.1 什么是栈&#xff1f;3.2 认识相关寄存器和汇编指令常见寄存器常用汇编指令 3.3 详解函数栈帧的创建和销毁3.3.1 函数的调用堆栈&#xff08;main函数也是被其它函数调用…

python用cv2画图(line, rectangle, text等)

Python做图像图形研究的时候&#xff0c;通常需要画很多辅助几何形状&#xff08;比如bounding box等&#xff09;。基于opencv的几何图形绘制具有易用性&#xff0c;而且天然能和numpy数组交互。 本文总结了几种常用的cv2画几何图形的方法&#xff0c;当一个简易的手册使用&a…

Go 定时任务实现

定时任务简介 定时任务是指按照预定的时间间隔或特定时间点自动执行的计划任务或操作。这些任务通常用于自动化重复性的工作&#xff0c;以减轻人工操作的负担&#xff0c;提高效率。在计算机编程和应用程序开发中&#xff0c;定时任务是一种常见的编程模式&#xff0c;用于周…

大数据疫情分析及可视化系统 计算机竞赛

文章目录 0 前言2 开发简介3 数据集4 实现技术4.1 系统架构4.2 开发环境4.3 疫情地图4.3.1 填充图(Choropleth maps)4.3.2 气泡图 4.4 全国疫情实时追踪4.6 其他页面 5 关键代码最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 大数据疫…

智慧社区大屏:连接社区生活的数字桥梁

随着科技的不断发展&#xff0c;智慧社区已经不再只是未来的概念&#xff0c;它已经在我们的眼前悄然崭露头角。智慧社区是一种基于数字技术的社区管理和生活方式&#xff0c;旨在提高社区的安全性、便利性和生活质量。而在这个数字化的社区中&#xff0c;智慧社区大屏起到了连…

Spire.Office for .NET 8.10.2 同步更新-Crk

Spire.Office for .NET是 E-iceblue 提供的企业级 Office .NET API 的组合。它包括Spire.Doc、Spire.XLS、Spire.Spreadsheet、Spire.Presentation、Spire.PDF、Spire.DataExport、Spire.OfficeViewer、Spire.PDFViewer、Spire.DocViewer、Spire.Barcode和Spire.Email。Spire.O…

一文深入搞懂ARM处理器架构

1、嵌入式处理器基础 典型的微处理器由控制单元、程序计数器&#xff08;PC&#xff09;、指令寄存器&#xff08;IR&#xff09;、数据通道、存储器等组成 。 指令执行过程一般分为&#xff1a; 取指&#xff1a; 从存储器中获得下一条执行的指令读入指令寄存器&#xff1…

对“Fork”做一个技术方面的简介

使用过 GitHub 的人大多知道它上面有个“Fork”的功能&#xff0c;用来将某个仓库克隆到你的账户之下&#xff0c;从而可以对其进行修改、衍生&#xff0c;也可以比较方便的将你的修改推回到原来的仓库&#xff08;所谓的上游&#xff09;。 随着 GitHub 的流行&#xff0c;我…

基于鹈鹕算法的无人机航迹规划-附代码

基于鹈鹕算法的无人机航迹规划 文章目录 基于鹈鹕算法的无人机航迹规划1.鹈鹕搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用鹈鹕算法来优化无人机航迹规划。 1.鹈鹕搜索算法 …

什么是微服务?与分布式又有什么区别?

什么是微服务&#xff0c;我们先从传统的单体结构进行了解&#xff0c;对两者进行对比。 单体结构 单体结构是一种传统的软件架构模式&#xff0c;它将应用程序划分为一组相互依赖的模块和组件。这些模块和组件通常都是构建在同一个平台上的&#xff0c;并且紧密耦合在一起。…

一款简简单单基于AIGC的文档翻译软件

一款文字/文件翻译的网站,支持多个领域的翻译&#xff0c;支持常见的语言翻译(韩/日/法/英/俄/德…),最大百分比的保持原文排版(及个别除外基本100%还原)。 新用户注册就有100页的免费额度&#xff0c;每月系统还会随机赠送翻译额度&#xff0c;说实话这比好多的企业要好的多了…

双十一电视盒子哪个牌子最好?老烧分享电视盒子品牌排行榜

在挑选电视盒子时很多人踩雷&#xff0c;我自己买过十来款电视盒子&#xff0c;存在不少问题&#xff0c;双十一这段时间身边买电视盒子的都会问我电视盒子哪个牌子最好&#xff0c;我今天来分享的是业内最专业的电视盒子品牌排行榜&#xff0c;双十一想买电视盒子可以从下面这…

人大金仓KingbaseES_V008R006C008B0014安装

人大金仓安装 一、安装前准备工作 1、硬件环境要求 KingbaseES支持通用X86_64、龙芯、飞腾、鲲鹏等国产CPU硬件体系架构。 2、软件环境要求 KingbaseES支持各种主流的Linux操作系统64位发行版本&#xff0c;包括CentOS、中标麒麟、银河麒麟、统信UOS、Deepin、凝思、中科方…

ConvNets 与 Vision Transformers:数学深入探讨

一、说明 我目睹了关于 Vision Transformer 的争论&#xff0c;讨论它们如何与 CNN 一样好或更好。我想知道我们是否也同样争论菠萝比西瓜好&#xff01;或者马比海豚更好&#xff1f;其中许多讨论往往缺乏具体性&#xff0c;有时可能会歪曲上下文。 作为背景&#xff0c;在快速…

【owt】p2p client mfc 工程梳理

1年前构建的,已经搞不清楚了。所以梳理下,争取能用较新的webrtc版本做测试。最早肯定用这个测试跑通过 【owt】p2p Signaling Server 运行、与OWT-P2P-MFC 交互过程及信令分析官方的mfc客户端 估计是构造了多个不同的webrc版本的客户端

Spring:常见的面试题和答案

1、什么是 Spring 框架&#xff1f;Spring 框架有哪些主要模块&#xff1f; Spring 框架是一个为 Java 应用程序的开发提供了综合、广泛的基础性支持的 Java 平台。 Spring 帮助开发者解决了开发中基础性的问题&#xff0c;使得开发人员可以专注于应用程序的开发。 Spring 框架…

Find My手机保护壳|苹果Find My与手机保护壳结合,智能防丢,全球定位

随着科技水平的快速发展&#xff0c;科技美容这一行业做为新型产业新生而出。时尚IT品牌随着市场的多元化发展。针对手机品牌和功能的增加而呈多样化&#xff0c;将手机保护壳按质地分有PC壳&#xff0c;皮革 &#xff0c;硅胶&#xff0c;布料&#xff0c;硬塑&#xff0c;皮套…