【数据结构】AVL树——平衡二叉搜索树

个人主页:东洛的克莱斯韦克-CSDN博客

祝福语:愿你拥抱自由的风

目录

二叉搜索树

AVL树概述

平衡因子

旋转情况分类

左单旋

右单旋

左右双旋

右左双旋

AVL树节点设计

AVL树设计

详解单旋

左单旋

右单旋

详解双旋

左右双旋

平衡因子情况如下

右左双旋

平衡因子情况如下


二叉搜索树

【C++】详解二叉搜索树-CSDN博客

AVL树概述

平衡树:左子树的高度等于右子树的高度

不平衡树:左子树的高度不等于等于右子树的高度

二叉搜索树很难是一颗平衡树。

对二叉树进行插入和删除的操作,或插入大量的数据不够随机,都会是使二叉搜索树不够平衡。

极端情况下,二叉树会退化成类似链表的结构,那么二叉搜索树查询数据的效率荡然无存。

在二叉树的基础上加入平衡的概念就是平衡二叉搜索树,也叫AVL树

AVL树也不是一颗绝对的平衡树,AVL树的平衡是相对的,它允许左子树和右子树的高度为 1 ,但不能超过 1

平衡是相对的很好理解,因为一个父亲节点最多只能有两个孩子节点,而数据又是一个一个插入的,所以一定会出现左子树和右子树高度差为 1 的情况。

B树可达到绝对平衡,因为B树是多叉结构——一个父亲节点有多个孩子节点

如果左子树和右子树的高度差为 2 就视为打破平衡

如果打破平衡,就需要通过旋转这一操作让左右子树的高度差小于等于 1 。

AVL树是保持一种相对平衡的状态,而不是绝对平衡。那么AVL树搜索数据的效率只能是接近O(logN)

AVL树只是保证了搜索效率的下限,而不是提高了上限

平衡因子

平衡因子这一概念并不是AVL树所必备的——从代码实现的角度来说,如果不加入平衡因子的概念理解起来会比较抽象。

平衡因子:让每个节点存一个整型,该整形值的大小等于右子树的高度减左子树的高度

平衡因子等于 0 左右子树平衡

平衡因子等于 1左右子树相对平衡,右树偏高

平衡因子等于 -1 :左右子树相对平衡,左树树偏高

平衡因子等于 2 -2左右子树不平衡

平衡因子的更新:

插入父亲节点的右边平衡因子加加,插入父亲节点的右边平衡因子减减

父亲节点更新后的平衡因子等于 1 或 -1 ,需要不断往上(溯源)更新,直到父亲节点的平衡因子为 0 或 更新至整棵树的根节点就停止更新

如果父亲节点的平衡因子为 2 或 -2 时,需要对这棵子树旋转,旋转后更新平衡因子

示例

旋转情况分类

旋转分为:

左单旋 右单旋  左右双旋  右左双旋

左单旋

:新节点插入较高右子树的右侧

具象图:

抽象图:

那么左单旋是怎么旋的呢?核心步骤为:

设父亲节点为:fathernode 孩子节点为:cur

cur的左孩子成为fathernode的右孩子,

再让fathernode成为cur的左孩子。

如下示意图

右单旋

:新节点插入较高左子树的左侧

具象图:

抽象图:

那么右单旋是怎么旋的呢?核心步骤为:

设父亲节点为:fathernode 孩子节点为:cur

cur的右孩子成为fathernode的左孩子,

再让fathernode成为cur的右孩子

如下示意图:

左右双旋

:新节点插入在较高左子树的右侧——先左单旋再右单旋

左右双旋的核心步骤为:

设父亲节点为:fathernode 

父亲的左孩子节点为:fathernodeL

父亲的左孩子节点的右孩子节点的为fathernodeLR

先让fathernodeL左单旋,再让fathernodeLR进行右单旋

这里小编直接上抽象图:

右左双旋

:新节点插入再较高右子树的左侧——先右单旋再左单旋

设父亲节点为:fathernode 

父亲的 右孩子节点为:fathernodeR

父亲的右孩子节点的左孩子节点的为fathernodeRL

先对fathernodeR进行右单旋,再对fathernode进行左单旋。

示意图:

AVL树节点设计

【C++】详解C++的模板-CSDN博客

AVL树的节点需要三个指针,分别指向左孩子节点,右孩子节点,父亲节点。指向父亲节点的指针是为了能溯源更新平衡因子。

需要一个整型存储平衡因子,平衡因子在构造函数的初始化列表中初始化为 0,因为新节点左右孩子都为空。

template <class K>
class AVLTreeNode
{
public:AVLTreeNode(const K& key) //构造函数:_key(key), _left(nullptr), _right(nullptr), _FatherNode(nullptr), _bf(0){}K _key; //键值   AVLTreeNode<K>* _left;//左孩子AVLTreeNode<K>* _right;//右孩子AVLTreeNode<K>* _FatherNode;//父亲  int _bf;//平衡因子};

AVL树设计

template <class K>
class AVLTree
{typedef AVLTreeNode<K> node; node* _root;public:AVLTree()  //构造函数,初始化为空树:_root(nullptr){}bool Insert(const K& key)//插入元素{
//if (nullptr == _root) //是否是空树{_root = new node(key);  return true;}
//node* cur = _root;node* fathernode = nullptr;while (cur)  //查找插入的位置,如果树中已经有要插入的值,则插入失败,{if (cur->_key < key){fathernode = cur;cur = cur->_right;}else if (cur->_key > key){fathernode = cur;cur = cur->_left;}else{return false;}}cur = new node(key); //新插入节点 if (fathernode->_key < cur->_key) //判断新节点应该是左孩子还是右孩子{fathernode->_right = cur;cur->_FatherNode = fathernode;}else{fathernode->_left = cur;cur->_FatherNode = fathernode;}//while (fathernode)//更新平衡因子{if (cur == fathernode->_left){fathernode->_bf--;}else  if (cur == fathernode->_right){fathernode->_bf++;}//if (fathernode->_bf == 0){// 更新结束break;}else if (fathernode->_bf == 1 || fathernode->_bf == -1){// 继续往上更新cur = fathernode;fathernode = fathernode->_FatherNode;}else if (fathernode->_bf == 2 || fathernode->_bf == -2){// 子树不平衡了,需要旋转if (fathernode->_bf == 2 && cur->_bf == 1){RevolveLeft(fathernode);//左单旋}else if (fathernode->_bf == -2 && cur->_bf == -1){RevolveRight(fathernode);//右单旋}else if (fathernode->_bf == 2 && cur->_bf == -1){RevolveRightLeft(fathernode); //右左双旋   }else if (fathernode->_bf == -2 && cur->_bf == 1){RevolveLeftRight(fathernode);//左右双旋}else{assert(false);   //平衡因子出问题了}break;}}return true;}}

下面通过代码的细节来深入理解旋转

详解单旋

左单旋

完整代码如下

void RevolveLeft(node *& fathernode)//左单旋      
{node* cur = fathernode->_right; //父亲节点的右孩子fathernode->_right = cur->_left; //更改指向关系if (cur->_left != nullptr) //特殊情况cur->_left->_FatherNode = fathernode;//更改指向关系cur->_FatherNode = fathernode->_FatherNode;//更改指向关系if (fathernode->_FatherNode != nullptr) //为空是特殊情况,{if (fathernode->_FatherNode->_right == fathernode){fathernode->_FatherNode->_right = cur;//更改指向关系}else{fathernode->_FatherNode->_left = cur;//更改指向关系}}cur->_left = fathernode;//更改指向关系fathernode->_FatherNode = cur;//更改指向关系fathernode->_bf = 0; //更新平衡因子cur->_bf = 0;}

处理指向关系时,一定不要忘了更改父亲的指向关系

经过左单旋之后,父亲节点和右孩子节点的平衡因子都为 0 ,可参考上文图示。

特殊情况如下,如果不处理特殊情况,程序很容易崩溃

右单旋

void RevolveRight(node *& fathernode) //右单旋
{node* cur = fathernode->_left; //父亲节点的左节点fathernode->_left = cur->_right;//更新指向关系if (cur->_right != nullptr) //特殊情况cur->_right->_FatherNode = fathernode;//更新指向关系cur->_FatherNode = fathernode->_FatherNode;//更新指向关系if (fathernode->_FatherNode != nullptr)//特殊情况{if (fathernode->_FatherNode->_right == fathernode){fathernode->_FatherNode->_right = cur;//更新指向关系}else{fathernode->_FatherNode->_left = cur;//更新指向关系}}cur->_right = fathernode;//更新指向关系fathernode->_FatherNode = cur;//更新指向关系fathernode->_bf = 0;//更新平衡因子cur->_bf = 0;
}

详解双旋

左右双旋

左右双旋只需复用左单旋和右单旋即可,但平衡因子的更新却比较麻烦

完整代码如下

	void RevolveLeftRight(node *& fathernode)//左右双旋    {node* fathernodeL = fathernode->_left; //父亲节点的左孩子节点node* fathernodeLR = fathernodeL->_right;//父亲节点的左孩子节点的右孩子节点int bf = fathernodeLR->_bf; //保存平衡因子,实际是为了判断是插入了fathernodeLR左边还是右边还是fathernodeLR本身插入RevolveLeft(fathernodeL);RevolveRight(fathernode);//更新平衡因子if (bf == 0){fathernode->_bf = 0;fathernodeL->_bf = 0;fathernodeLR->_bf = 0;}else if (bf == -1){fathernode->_bf = 1;fathernodeL->_bf = 0;fathernodeLR->_bf = 0;}else if (bf == 1){fathernodeL->_bf = -1;fathernode = 0;fathernodeLR = 0;}else{assert(false);}}

平衡因子情况如下

右左双旋

完整代码如下

	void RevolveRightLeft(node *& fathernode) //右左双旋 {node* fathernodeR = fathernode->_right; node* fathernodeRL = fathernodeR->_left;int bf = fathernodeRL->_bf;RevolveRight(fathernodeR);RevolveLeft(fathernode);if (bf == 0){fathernode->_bf = 0;fathernodeR->_bf = 0;fathernodeRL->_bf = 0;}else if (bf == 1){fathernode->_bf = -1;fathernodeR->_bf = 0;fathernodeRL->_bf = 0;}else if (bf == -1){fathernodeR->_bf = 1;fathernode->_bf = 0;fathernodeRL->_bf = 0;}else{assert(false); }}

平衡因子情况如下

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

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

相关文章

阿里开源React应用动效解决方案:ant-motion

ant-motion&#xff1a;简化动效开发&#xff0c;提升用户体验 - 精选真开源&#xff0c;释放新价值。 概览 Ant Motion是由Ant Design团队精心打造&#xff0c;专为React应用设计的动画规范和组件库。它不仅仅是一套动画规范&#xff0c;更是一个完整的解决方案&#xff0c;旨…

C# yolov8 TensorRT Demo

C# yolov8 TensorRT Demo 目录 效果 说明 项目 代码 下载 效果 说明 环境 NVIDIA GeForce RTX 4060 Laptop GPU cuda12.1cudnn 8.8.1TensorRT-8.6.1.6 版本和我不一致的需要重新编译TensorRtExtern.dll&#xff0c;TensorRtExtern源码地址&#xff1a;https://githu…

2024年JAVA、C++、Pyhton学哪种语言更容易进国央企?

对于不同编程语言在进入国有企业的观点大体是正确的&#xff0c;不过在实际选择时还需考虑一些因素。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信…

【CSDN独家公开】Python解析.SchDoc格式文件转换为json文件

前情提要 因工作需求&#xff0c;需要解析.SchDoc格式文件&#xff0c;提取文本和位置关系&#xff0c;通常方式是转换为图片或PDF&#xff0c;再进行OCR&#xff0c;但是这样识别精度太低了 Github找了好些项目&#xff0c;都不支持 PyAltium不支持 https://github.com/plu…

apexcharts数据可视化之饼图

apexcharts数据可视化之饼图 有完整配套的Python后端代码。 本教程主要会介绍如下图形绘制方式&#xff1a; 基础饼图单色饼图图片饼图 基础饼图 import ApexChart from react-apexcharts;export function SimplePie() {// 数据序列const series [44, 55, 13, 43, 22]// …

APM2.8内置罗盘校准

如果你有外置罗盘&#xff0c;可以不用校准内置罗盘&#xff0c;可以忽略此文。推荐使用外置罗盘&#xff0c;内置罗盘容易受干扰。 使用内置罗盘需要插入飞控GPS接口旁边的跳线帽。如图&#xff1a; 如果要使用内置罗盘&#xff0c;而又加了GPS的&#xff0c;记得一定要把GPS…

【错误记录】HarmonyOS 运行报错 ( Failure INSTALL _PARSE _FAILED _USESDK _ERROR )

文章目录 一、报错信息二、问题分析三、解决方案 一、报错信息 在 DevEco Studio 中 , 使用 远程设备 , 向 P40 Failure[INSTALL_PARSE_FAILED_USESDK_ERROR] compileSdkVersion and releaseType of the app do not match the apiVersion and releaseType on the device. 二、…

SpringMVC枚举类型字段处理

在日常的项目开发中经常会遇到一些取值范围固定的字段&#xff0c;例如性别、证件类型、会员等级等&#xff0c;此时我们可以利用枚举来最大程度减少字段的乱定义&#xff0c;统一管理枚举的值。 SpringMVC中对于枚举也有默认的处理策略&#xff1a; 对于RequestParam&#xf…

静态测试---基于WorkList的活跃变量分析

本文主要用于记录在活跃变量分析实验中的报错及解决&#xff0c;涉及静态测试的详细原理内容较少&#xff0c;编译运行底层逻辑偏多。 一、实验要求 1&#xff09;使用llvm基于框架实现一个基于WorkList的活跃变量分析demo。变量在某个程序点有两种状态&#xff0c;live 或 dea…

利用 Scapy 库编写源路由攻击脚本

一、介绍 源路由攻击是一种网络攻击方法&#xff0c;攻击者通过利用IP数据包中的源路由选项来控制数据包的传输路径&#xff0c;从而绕过安全设备或防火墙&#xff0c;直接访问目标系统。源路由功能允许数据包的发送方指定数据包通过的路径&#xff0c;而不是由路由器根据路由…

Xshell 5(xmanager5)报错

总结 所有的错误都是因为Xshell版本太低&#xff0c;与新的Linux系统不兼容导致的。 所以解决办法都是使用Xshell7 XShell 7 &#xff08;解压、运行绿化.bat&#xff09; https://pan.baidu.com/s/151W_MeLrrceUZQIFiNlMdg?pwd8888错误1&#xff1a;找不到匹配的host key算…

【LeetCode刷题】滑动窗口解决问题:串联所有单词的子串(困难)、最小覆盖子串(困难)

【LeetCode刷题】Day 10 题目1&#xff1a;30. 串联所有单词的子串&#xff08;困难&#xff09;思路分析&#xff1a;思路1&#xff1a;滑动窗口哈希map 题目2&#xff1a;LCR 017.最小覆盖子串思路分析思路1&#xff1a;滑动窗口哈希表 题目1&#xff1a;30. 串联所有单词的子…

基于51单片机的直流电机调速设计

一.硬件方案 本系统采用STC89C51控制输出数据&#xff0c;由单片机IO口产生PWM信号&#xff0c;送到直流电机&#xff0c;直流电机通过测速电路将实时转速送回单片机&#xff0c;进行转速显示&#xff0c;从而实现对电机速度和转向的控制&#xff0c;达到直流电机调速的目的。…

qt把虚拟键盘部署到arm开发板上(imx6ull)

分为了qt官方配置的虚拟键盘以及各路大神自己开源的第三方键盘&#xff0c;我本来想尝试利用官方键盘结果一直失败&#xff0c;最后放弃了&#xff0c;后面我用的第三方键盘参考了如下文章&#xff1a; https://blog.csdn.net/2301_76250105/article/details/136441243 https…

git 学习随笔

git 学习随笔 基本概念 git 对待数据类似快照流的形式而不是类似 cvs 那样的纪录文件随时间逐步积累的差异 git 中所有数据在存储钱都会计算校验和&#xff08;hash) 三种状态&#xff1a;已提交(committed)&#xff0c;已修改(modified)&#xff0c;已暂存(staged)。 add…

二叉树习题精讲-单值二叉树

单值二叉树 965. 单值二叉树 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/univalued-binary-tree/description/ 判断这里面的所有数值是不是一样 方案1&#xff1a;遍历 方案2&#xff1a;拆分子问题 /*** Definition for a binary tree node.* struc…

k8s群集调度之 pod亲和 node亲和 标签指定

目录 一 调度约束 1.1K8S的 List-Watch 机制 ⭐⭐⭐⭐⭐ 1.1.1Pod 启动典型创建过程 二、调度过程 2.1Predicate&#xff08;预选策略&#xff09; 常见的算法 2.2priorities&#xff08;优选策略&#xff09;常见的算法 三、k8s将pod调度到指定node的方法 3.1指…

最强端侧多模态模型MiniCPM-V 2.5,8B 参数,性能超越 GPT-4V 和 Gemini Pro

前言 近年来&#xff0c;人工智能领域掀起了一股大模型热潮&#xff0c;然而大模型的巨大参数量级和高昂的算力需求&#xff0c;限制了其在端侧设备上的应用。为了打破这一局限&#xff0c;面壁智能推出了 MiniCPM 模型家族&#xff0c;致力于打造高性能、低参数量的端侧模型。…

【深度学习】xformers与pytorch的版本对应关系

https://github.com/facebookresearch/xformers/tree/v0.0.23 找tag&#xff1a; tag下面写了对应关系&#xff1a; 安装指令就是&#xff1a; pip install xformers0.0.23 --no-deps -i https://download.pytorch.org/whl/cu118

react ant 表格实现 拖拽排序和多选

项目背景 : react ant 要实现 : 有多选功能(实现批量删除 , 也可以全选) 可以拖拽(可以复制 , 方便顶部的搜索功能) 要实现效果如下 1 这是最初的拖拽功能实现 , 不能复制表格里的内容 , 不符合要求 2 更改了ROW的内容 , 实现了可以复制表格内容 代码 //控制是否可以选中表格…