平衡二叉搜索树/AVL树

VAL树的特性

  1. 左右子树高度差的绝对值不超过1。(即左右子树高度差取值为-1,0,1)
  2. 且左右子树均为VAL树
  3. 右子树的值大于左子树的值

在搜索二叉树中我们提及了搜索二叉树的退化问题。

当有序(升序或降序)地插入时,搜索二叉树就会出现下图的情况,搜索次数从树的高度次退化成n次。而平衡搜索二叉树就解决了这一问题。

 

平衡方法

在解决方案中,左右子树的差值被定为平衡因子,所以上述例子用平衡二叉搜索树的角度来看如下图。此时出现了不被允许的2和-2,意味着平衡被打破,需要进行调整平衡。

插入时平衡因子的变化逻辑

以下绿色方框代表一棵子树,h为其高度,且h>=0,具体节点上方的数字为平衡因子。

因为插入节点,导致60这个节点的左右子树失衡了。

 

        如图我们可以看到,根据插入位置的不同,其父节点的平衡因子的变化也不同。因为此处我使用的平衡因子 = 右子树高度 - 左子树高度。所以当插入到右子树时,父节点平衡因子+1,插入到左子树时,父节点平衡因子-1。

了解完插入时平衡因子的变化,我们再来了解一下变化的两种情况。

(1)插入后父节点平衡因子变为1或-1的,那么其原来的平衡因子是0,意味着该父节点原本平衡,插入节点后打破了平衡使该父节点为根的整棵子树高度产生变化,此时平衡因子需要继续向上更新,于是60这个节点也受其影响改变了,此时出现了不被允许的失衡,此时就要旋转调整平衡。

(2)第二种情况比较简单,插入后父节点平衡因子变为0,意味着其原本有一些失衡,但还在允许范围内,但插入后完全平衡了,此时无需再继续更新,因为高度没有改变。

总结:亚健康(-1、1)到健康(0)不用管,反之健康到亚健康要去向上更新平衡因子,因为其父节点可能直接就生病了。

// 在AVL树中插入值为data的节点bool Insert(const T& data){if (_pRoot == nullptr){_pRoot = new Node(data);return true;}Node* parent = nullptr;Node* cur = _pRoot;while (cur){if (data > cur->_data){parent = cur;cur = cur->_pRight;}else if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else{return false;}}cur = new Node(data);if (parent->_data < data){parent->_pRight = cur;}else{parent->_pLeft = cur;}cur->_pParent = parent;//调整平衡因子while (parent){if (cur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}//插入后恰好平衡了,跳出循环if (parent->_bf == 0){break;}    //高度出现变化但还未失衡,向上走进行判断else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_pParent;}	//已经失衡需要进行调整else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){//右单旋RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){//左单旋RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){//右左双旋RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){//左右双旋RotateLR(parent);}break;}else{//理论而言不可能出现这个情况(此时小于-2或大于2)assert(false);}}return true;}

旋转调整 

有了前面插入时平衡因子变化逻辑的铺垫,失衡时的情况就简化成了:

失衡节点无非是-2或2,造就失衡节点的子节点无非是-1,1(产生高度变化)。排列组合后就出现了四种情况。于是出现四种旋转方式与其一一对应。

 右单旋

此时的失衡情况为:左左(插入在失衡节点的左节点的左子树中),此时失衡节点平衡因子为-2,产生高度变化的子节点平衡因子为-1。

所以右单旋适用于“左左”,即parent的平衡因子为-2,高度变化的子节点的平衡因子为-1。

 右单旋过程:
 

可以看到,调整后30和60这两个节点的平衡因子都为0了。

那么为什么要这么做? 

现在我们先来分析一下他们的大小关系。

比较大小后发现,60非常适合当作30的右子树根节点,同时又做b子树的父节点,因为60>b子树任意值>30。

所以右单旋就是将失衡节点及其右子树下压,当作高度出现变化的节点的右子树,再连接上b子树和60这个节点,就使30的左右子树高度一致了。左子树高度是h+1(1为插入的节点),右边也是h+1(1为原父节点),此时30成为新父节点。

// 右单旋void RotateR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;subL->_pRight = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subL;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subL;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subL;}else{temp->_pRight = subL;}subL->_pParent = temp;}pParent->_bf = subL->_bf = 0;}

左单旋

与右单旋同理,不过多赘述。

左单旋适用于“右右”,即parent的平衡因子为2,高度变化的子节点的平衡因子为1。

旋转过程: 

// 左单旋void RotateL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL)subRL->_pParent = pParent;subR->_pLeft = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subR;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subR;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subR;}else{temp->_pRight = subR;}subR->_pParent = temp;}pParent->_bf = subR->_bf = 0;}

左右双旋

如图所示,当出现插入节点在失衡节点(90)其左孩子(30)的右孩子(60)下的子树时,就需要用到左右双旋,单旋是无法解决的。

即:失衡节点平衡因子为-2,且其左孩子的平衡因子为1时使用左右双旋,以下简称“左右”。

第一步,我们对30为根的子树进行左单旋。(第一次旋转后,失衡的状况从“左右”变成了可以使用右单旋的“左左”)

 第二步,我们对90为根的子树进行右单旋。

 

 代码实际上是对左单旋和右单旋的复用,然后再处理节点间的连接关系。

// 左右双旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);subLR->_bf = 0;if (bf == 1){pParent->_bf = 0;subL->_bf = -1;}else if (bf == -1){pParent->_bf = 1;subL->_bf = 0;}else{pParent->_bf = 0;subL->_bf = 0;}}

右左双旋

失衡节点平衡因子为2,且其左孩子的平衡因子为-1时使用左右双旋。

第一步,我们对90为根的子树进行右单旋。(第一次旋转后,失衡的状况从“右左”变成了可以使用左单旋的“右右”)

 第二步,我们对30为根的子树进行左单旋。

 

// 右左双旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);subRL->_bf = 0;if (bf == 1){pParent->_bf = -1;subR->_bf = 0;}else if (bf == -1){pParent->_bf = 0;subR->_bf = 1;}else{pParent->_bf = 0;subR->_bf = 0;}}

完整代码实现

#pragma once
#include<assert.h>
#include<vector>
#include<iostream>
using namespace std;namespace VAL
{template<class T>struct AVLTreeNode{AVLTreeNode(const T& data = T()): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _pLeft;AVLTreeNode<T>* _pRight;AVLTreeNode<T>* _pParent;T _data;int _bf;   // 节点的平衡因子};// AVL: 二叉搜索树 + 平衡因子的限制template<class T>class AVLTree{typedef AVLTreeNode<T> Node;public:AVLTree(): _pRoot(nullptr){}Node* Find(const T& data){Node* cur = _pRoot;while (cur){if (cur->_data < data){cur = cur->_pRight;}else if (cur->_data > data){cur = cur->_pLeft;}else{return cur;}}return nullptr;}// 在AVL树中插入值为data的节点bool Insert(const T& data){if (_pRoot == nullptr){_pRoot = new Node(data);return true;}Node* parent = nullptr;Node* cur = _pRoot;while (cur){if (data > cur->_data){parent = cur;cur = cur->_pRight;}else if (data < cur->_data){parent = cur;cur = cur->_pLeft;}else{return false;}}cur = new Node(data);if (parent->_data < data){parent->_pRight = cur;}else{parent->_pLeft = cur;}cur->_pParent = parent;//调整平衡因子while (parent){if (cur == parent->_pLeft){parent->_bf--;}else{parent->_bf++;}//插入后恰好平衡了,跳出循环if (parent->_bf == 0){break;}    //高度出现变化但还未失衡,向上走进行判断else if (parent->_bf == 1 || parent->_bf == -1){cur = parent;parent = parent->_pParent;}	//已经失衡需要进行调整else if (parent->_bf == 2 || parent->_bf == -2){if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{// 理论而言不可能出现这个情况assert(false);}}return true;}// AVL树的验证bool IsAVLTree(){return _IsAVLTree(_pRoot);}int Size(){return _Size(_pRoot);}void InOrder(){_InOrder(_pRoot);cout << endl;}size_t Height(){return _Height(_pRoot);}private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_pLeft);cout << root->_data << endl;_InOrder(root->_pRight);}bool _IsAVLTree(Node* root){if (root == nullptr)return true;int leftHeight = _Height(root->_pLeft);int rightHeight = _Height(root->_pRight);// 不平衡if (abs(leftHeight - rightHeight) >= 2){cout << root->_data << endl;return false;}// 顺便检查一下平衡因子是否正确if (rightHeight - leftHeight != root->_bf){cout << root->_data << endl;return false;}return _IsAVLTree(root->_pLeft)&& _IsAVLTree(root->_pRight);}size_t _Height(Node* _pRoot){if (_pRoot == nullptr) return 0;return max(_Height(_pRoot->_pLeft), _Height(_pRoot->_pRight)) + 1;}int _Size(Node* root){return root == nullptr ? 0 : _Size(root->_pLeft) + _Size(root->_pRight) + 1;}// 右单旋void RotateR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;pParent->_pLeft = subLR;if (subLR)subLR->_pParent = pParent;subL->_pRight = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subL;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subL;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subL;}else{temp->_pRight = subL;}subL->_pParent = temp;}pParent->_bf = subL->_bf = 0;}// 左单旋void RotateL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;pParent->_pRight = subRL;if (subRL)subRL->_pParent = pParent;subR->_pLeft = pParent;Node* temp = pParent->_pParent;pParent->_pParent = subR;//pParent有可能为根,右单旋后应该更新根节点指向。if (pParent == _pRoot){_pRoot = subR;_pRoot->_pParent = nullptr;}else{if (temp->_pLeft == pParent){temp->_pLeft = subR;}else{temp->_pRight = subR;}subR->_pParent = temp;}pParent->_bf = subR->_bf = 0;}// 右左双旋void RotateRL(Node* pParent){Node* subR = pParent->_pRight;Node* subRL = subR->_pLeft;int bf = subRL->_bf;RotateR(subR);RotateL(pParent);subRL->_bf = 0;if (bf == 1){pParent->_bf = -1;subR->_bf = 0;}else if (bf == -1){pParent->_bf = 0;subR->_bf = 1;}else{pParent->_bf = 0;subR->_bf = 0;}}// 左右双旋void RotateLR(Node* pParent){Node* subL = pParent->_pLeft;Node* subLR = subL->_pRight;int bf = subLR->_bf;RotateL(subL);RotateR(pParent);subLR->_bf = 0;if (bf == 1){pParent->_bf = 0;subL->_bf = -1;}else if (bf == -1){pParent->_bf = 1;subL->_bf = 0;}else{pParent->_bf = 0;subL->_bf = 0;}}private:Node* _pRoot;};
}

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

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

相关文章

摸鱼大数据——Spark基础——Spark环境安装——Spark Local[*]搭建

一、虚拟机配置 查看每一台的虚拟机的IP地址和网关地址 查看路径: cat /etc/sysconfig/network-scripts/ifcfg-ens33 2.修改 VMware的网络地址: 使用VMnet8 3.修改windows的对应VMware的网卡地址 4.通过finalshell 或者其他的shell连接工具即可连接使用即可, 连接后, 测试一…

AD PCB板子裁剪与泪滴设置

在剪裁板子时。首先&#xff0c;选择选择板子的机械层&#xff0c;之后选择画线。在原来的板子上画上自己想要裁剪的图形。如下下图 之后&#xff0c;选择按照所画的线裁剪板子即可&#xff0c;如下 在焊接PCB时&#xff0c;为了防止多次焊接导至焊盘脱落可以加大焊点的接触面积…

ESP32-C3模组上跑通MQTT(6)—— tcp例程(1)

接前一篇文章:ESP32-C3模组上跑通MQTT(5) 《ESP32-C3 物联网工程开发实战》 一分钟了解MQTT协议 ESP32 MQTT API指南-CSDN博客 ESP-IDF MQTT 示例入门_mqtt outbox-CSDN博客 ESP32用自签CA进行MQTT的TLS双向认证通信_esp32 mqtt ssl-CSDN博客 特此致谢! 本回开始正式讲…

华润万家超市卡怎么用?

华润的礼品卡不仅能线下门店使用&#xff0c;还能直接叫送货上门 我最近用积分兑了几张华润卡&#xff0c;但是又没有购物需求&#xff0c;送朋友吧面值又不大&#xff0c;朋友也说用不上 最后朋友建议我在收卡云上把卡出掉&#xff0c;我试了下92折出掉了&#xff0c;价格还…

代码随想录算法训练营第四十七天| 188.买卖股票的最佳时机IV ,309.最佳买卖股票时机含冷冻期 ,714.买卖股票的最佳时机含手续费

188. 买卖股票的最佳时机 IV - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int k, int[] prices) {int[][] dp new int[prices.length][2*k];for(int i0;i<2*k;i){if(i%2 0){dp[0][i] -prices[0];}else{dp[0][i] 0;} }for(int i1;i…

综合项目实战--jenkins节点模式

一、DevOps流程 DevOps是一种方法论,是一系列可以帮助开发者和运维人员在实现各自目标的前提下,向自己的客户或用户交付最大化价值及最高质量成果的基本原则和实践,能让开发、测试、运维效率协同工作的方法。 DevOps流程(自动化测试部分) DevOps完整流程 二、gitee+j…

计算Dice损失的函数

计算Dice损失的函数 def Dice_loss(inputs, target, beta1, smooth 1e-5):n,c, h, w inputs.size() #nt,ht, wt, ct target.size() #nt,if h ! ht and w ! wt:inputs F.interpolate(inputs, size(ht, wt), mode"bilinear", align_cornersTrue)temp_inputs t…

LLaMA-Factory安装

安装代码 https://github.com/echonoshy/cgft-llm/blob/master/llama-factory/README.md https://github.com/hiyouga/LLaMA-Factory/tree/mainLLaMA-Factoryhttps://github.com/hiyouga/LLaMA-Factory/tree/main 【大模型微调】- 使用Llama Factory实现中文llama3微调_哔哩…

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍

TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍 添加 一个PLC,设置PLC的IP地址,如下图所示, 添加全局DB块,新建几个变量,如下图所示, 在数据块中添加了 tag1 …… tag6 ,共 6 个浮点数类型的变量,用来接收通过 WinCC 从 Excel 文件中读取的数据。 添加 HMI…

gdb-dashboard:用Python重塑GDB调试体验

gdb-dashboard&#xff1b;一目了然的GDB调试&#xff0c;尽在掌控之中- 精选真开源&#xff0c;释放新价值。 概览 gdb-dashboard是一个用Python编写的模块化视觉界面&#xff0c;为GNU Debugger&#xff08;GDB&#xff09;提供了一个现代化的工作空间。它通过集成多个面板和…

数据平台发展史-从数据仓库数据湖到数据湖仓

做数据的同学经常听到一些数据相关的术语&#xff0c;常见的包括数据仓库&#xff0c;逻辑数据仓库&#xff0c;数据湖&#xff0c;数据湖仓/湖仓一体&#xff0c;数据网格 data mesh,数据编织 data fabric等. 笔者在这里回顾了下数据平台的发展史&#xff0c;也介绍和对比了下…

【QT】按钮类控件 显示类控件

目录 按钮类控件 Push Button 设置按钮图标 按钮设置快捷键 设置鼠标点击按钮重复触发 Radio Button 单选框分组 Check Box 显示类控件 Label 常用属性 设置文本格式 给Label设置图片 Label标签设置边框 设置文本对齐方式 设置文本自动换行 设置文本缩进 设置…

《概率论与数理统计》期末笔记_下

目录 第4章 随机变量的数字特征 4.1 数学期望 4.2 方差 4.3 常见分布的期望与方差 4.4 协方差与相关系教 第5章 大数定律和中心极限定理 5.1 大数定律 5.2 中心极限定理 第6章 样本与抽样分布 6.1 数理统汁的基本概念 6.2 抽样分布 6.2.1 卡方分布 6.2.2 t分布 6.…

java基于ssm+jsp 高校毕业生就业满意度调查统计系统

1用户前台功能模块 高校毕业生就业满意度调查统计系统&#xff0c;在高校毕业生就业满意度调查统计系统可以查看首页、问卷、就业咨询、试卷列表、新闻资讯、留言反馈、我的、跳转到后台等内容&#xff0c;如图1所示。 图1系统首页界面图 用户登录、用户注册&#xff0c;通过…

LabVIEW代码性能优化

优化LabVIEW代码以提高软件性能是确保系统高效运行的关键。通过分析代码结构、数据管理、并行处理、内存使用和硬件资源的有效利用&#xff0c;我们可以从多个角度提升LabVIEW程序的执行速度和稳定性。 代码结构优化 模块化编程 将复杂的程序分解成多个子VI&#xff0c;每个子V…

LabVIEW编程控制ABB机械臂

使用LabVIEW编程控制ABB机械臂是一项复杂但十分有价值的任务。通过LabVIEW&#xff0c;可以实现对机械臂的精确控制和监控&#xff0c;提升自动化水平和操作效率。 1. 项目规划和硬件选型 1.1 确定系统需求 运动控制&#xff1a;确定机械臂需要执行的任务&#xff0c;如抓取、…

嵌入式Linux系统编程 — 5.2 Linux系统时间与日期

目录 1 了解Linux系统时间 1.1 几种常用的时间 1.2 如何查看几种常用的时间 1.3 Linux 系统中的时间 2 time、gettimeofday获取时间 2.1 time函数 2.2 ​​​​​​​gettimeofday函数&#xff1a; 2.3 示例程序 3 时间转换函数 3.1 ctime与ctime_r函数 3.2 localti…

Unity之自定义Text组件默认属性值

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之自定义Text组件默认属性值 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01;…

普通集群与镜像集群配置

一. 环境准备 关闭防火墙和selinux&#xff0c;进行时间同步 rabbitmq-1 Rocky_linux9.4 192.168.226.22rabbitmq-2Rocky_linux9.4192.168.226.23rabbitmq-3Rocky_linux9.4192.168.226.24 修改主机名#192.168.226.22 hostnamectl set-hostname rabbitmq-1#192.168.226.22 ho…

isalpha()方法——判断字符串是否只由字母组成

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 isalpha()方法用于判断字符串是否只由字母组成。isalpha()方法的格式如下&#xff1a; str.isalpha() 如果字符串中至少有一个字符并且所…