平衡二叉树(AVL树)C++

目录

AVL树的概念

AVL树的节点结构

AVL树的插入

更新平衡节点

代码实现

AVL树的旋转

左单旋

右单旋

左右双旋

右左双旋

AVL树的删除

AVL树的查找

AVL树的高度

AVL树的判定

AVL树的遍历


AVL树的概念

二叉排序(搜索)树,虽然可以缩短查找的效率,但是在数据接近有序的时候,二叉排序树会退化成为单支树(斜树),相当于在顺序表中查找元素,效率低下。

因此,两位俄罗斯的数学家发明了一种解决上述问题的办法,向二叉搜索树插入新的节点后,通过调整保证左右子树的高度差绝对值不大于1,从而降低树的高度,减少平均搜索长度。

平衡二叉树(Balanced Binary Tree)是一种特殊的二叉排序树,又称AVL树。

AVL树的性质:

1.AVL树是二叉排序树

2.左右子树高度之差(即平衡因子)的绝对值不超过1,即平衡因子可能为-1,0,1,

AVL树的节点结构

用KV模型定义二叉树,把节点定义成三叉链结构,因为构造的新节点左右子树都为空树,所以平衡因子设为0

平衡因子:右子树的高度-左子树的高度

template<class K,class V>
struct AVLTreeNode 
{//存储的键值对pair<K, V> _kv;//平衡因子(balance factor)int _bf;//存储的三叉链AVLTreeNode<K, V>* _parent;//方便找到父节点AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;//构造函数AVLTreeNode(const pair<K, V>& kv):_kv(kv), _parent(nullptr), _left(nullptr), _right(nullptr), _bf(0){}
};

AVL树的插入

更新平衡节点

AVL树插入结点时有以下三个步骤:

1.按照二叉搜索树的插入方法,找到待插入位置。
2.找到待插入位置后,将待插入结点插入到树中。
3.更新平衡因子,如果出现不平衡,则需要进行旋转。

与二叉搜索树相比,多了更新平衡因子这个步骤。因为插入节点后树的高度可能会变化

更新父节点的平衡因子(一定要更新)

1.左边插入:parent的平衡因子--

2.右边插入:parent的平衡因子++

根据父节点的平衡因子,判断是否要更新祖先节点的平衡因子

1.如果父节点bf==0,说明父节点原来一定是-1或+1,插入的节点弥补了原来的空缺,树的高度没有改变,不用向上更新

2.如果为+-1,说明平衡因子从0变成了-1,+1,高度改变,所以要向上更新

3.如果为+-2,说明该父节点就是最小不平衡子树的根节点,不用再向上查找

 

 

代码实现

class AVLTree
{typedef AVLTreeNode<K, V> Node;
private:Node* _root;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 (cur->_kv.first < kv.first) {parent = cur;cur = cur->_left;}else if (cur->_kv.first > kv.first) {parent = cur;cur = cur->_right;}else {return false;}}cur = new Node(kv);//链接并更新if (cur->_kv.first < parent->_kv.first) {parent->_left = cur;cur->_parent = parent;parent->_bf--;}else{parent->_right = cur;cur->_parent = parent;parent->_bf++;}while (parent)//最远要更新到根节点{if (parent->_bf == 0) {break;}else if (abs(parent->_bf) == 1){cur = cur->_parent;parent = parent->_parent;//向上查找}else if(abs(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){rotateLR(parent);}else if (parent->_bf == 2 && cur->bf == -1){rotateRL(parent);}break;}else{//如果程序走到这里,说明在此之前就有不平衡的子树assert(false);}}return true;}
};

AVL树的旋转

左单旋

什么时候需要用到左旋操作呢?

当 parent 的平衡因子为 2,cur 的平衡因子为 1 时,需要进行左单旋。

并且经过左单旋后,树的高度没有发生变化,所以左单旋后无需继续往上更新平衡因子。

请添加图片描述

在这里插入图片描述

 在这里插入图片描述

 步骤:

  • 先让 subR 的左子树(subRL)作为 parent 的右子树。
  • 然后让 parent 作为 subR 的左子树。
  • 接下来让 subR 作为整个子树的根。
  • 最后更新平衡因子。

 代码实现

void rotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;//1.建立subRL与parent之间的关系//左子树滑动parent->_right = subRL;if (subRL) {subRL->_parent = parent;}//2.建立subR和parent之间的关系//更新右子树的左子树subR->_left = parent;parent->_parent = subR;//3.建立pparent和subR之间的关系//与上一个节点建立连接if (parent == _root){_root = subR;subR->_parent == nullptr;}else{subR->_parent = pparent;if (parent = pparent->_left) {pparent->_left = subR;}else{pparent->_right = subR;}}//更新平衡因子parent->_bf = 0;subR->_bf = 0;}

右单旋

请添加图片描述

  • 先让 subL 的右子树(subLR)作为 parent 的左子树。
  • 然后让 parent 作为 subL 的右子树。
  • 接下来让 subL 作为整个子树的根。
  • 最后更新平衡因子。
void rotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;//滑动parent->_left = subLR;if (subLR) {subLR->_parent = parent;}//更新左子树的右子树subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else {subL->_parent == pparent;if (parent = pparent->_left) {pparent->_left = subL;}else{pparent->_right = subL;}}parent->_bf = 0;subL->_bf = 0;}

左右双旋

(1)以30为节点进行左单旋

在这里插入图片描述

 (2)以90为节点进行右单旋

 

左右双旋后,平衡因子的更新随着 subLR 原始平衡因子的不同分为以下三种情况:

(1)当 subLR 原始平衡因子是 -1 时,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 1、0、0

在这里插入图片描述

(2)当 subLR 原始平衡因子是 1 时,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 0、-1、0

(3)当 subLR 原始平衡因子是 0 时(说明 subLR 为新增结点),左右双旋后 parent、subL、subLR 的平衡因子分别更新为0、0、0

可以看到,经过左右双旋后,树的高度没有发生变化,所以左右双旋后无需继续往上更新平衡因子。

代码实现:

void rotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;rotateL(subL);//parent并不为最小不平衡子树的根,所以要在旋转以后重新赋值rotateR(parent);//各点的bf值由调整前subLR的bf值决定if (_bf == 0) {parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}

右左双旋

(1)以 90 为旋转点进行右单旋

在这里插入图片描述

(2)以 30 为旋转点进行左单旋

 

右左双旋后,平衡因子的更新随着 subLR 原始平衡因子的不同分为以下三种情况:、

(1)当 subRL 原始平衡因子是 1 时,左右双旋后 parent、subR、subRL 的平衡因子分别更新为 -1、0、0

 

(2)当 subRL 原始平衡因子是 -1 时,左右双旋后 parent、subR、subRL 的平衡因子分别更新为 0、1、0

 

(3)当 subRL 原始平衡因子是 0 时(说明 subRL为新增结点),左右双旋后 parent、subR、subRL 的平衡因子分别更新为0、0、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 = 0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{assert(false);}}

AVL树的删除

要进行结点的删除,首先需要在树中找到对应key值的结点,寻找待删除结点的方法和二叉搜索树相同

1.先找到待删除的结点。
2.若找到的待删除结点的左右子树均不为空,则需要使用替换法进行删除。

替换法删除指的是:让待删除结点左子树当中key值最大的结点,或待删除结点右子树当中值最小的结点代替待删除结点被删除(代码中以后者为例),然后再将待删除结点的key值以及value值都改为代替其被删除的结点的值即可。

也就是说,我们最终找到的实际被删除的结点的左右子树当中至少有一个为空树。

在找到实际需要被删除的结点后,我们先不进行实际的删除,而是先进行平衡因子的更新,不然后续更新平衡因子时特别麻烦(已经尝试过),而更新平衡因子时的规则与插入结点时的规则是相反的,更新规则如下:

1.删除的结点在parent的右边,parent的平衡因子− − 。
2.删除的结点在parent的左边,parent的平衡因子+ +。

并且每更新完一个结点的平衡因子后,都需要进行以下判断:

1.如果parent的平衡因子等于-1或者1,表明无需继续往上更新平衡因子了。
2.如果parent的平衡因子等于0,表明还需要继续往上更新平衡因子。
3.如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。

在旋转处理时,分为以下6种情况

1.当parent的平衡因子为-2,parent的左孩子的平衡因子为-1时,进行右单旋。
2.当parent的平衡因子为-2,parent的左孩子的平衡因子为1时,进行左右双旋。
3.当parent的平衡因子为-2,parent的左孩子的平衡因子为0时,也进行右单旋,但此时平衡因子调整与之前有所不同,具体看代码。
4.当parent的平衡因子为2,parent的右孩子的平衡因子为-1时,进行右左双旋。
5.当parent的平衡因子为2,parent的右孩子的平衡因子为1时,进行左单旋。
6.当parent的平衡因子为2,parent的右孩子的平衡因子为0时,也进行左单旋,但此时平衡因子调整与之前有所不同,具体看代码。

代码实现:

bool Erase(const K& key){//用于遍历二叉树Node* cur = parent;Node* parent = nullptr;//用于标记实际删除的节点和父节点Node* deletePos = nullptr;Node* deleteparentPos = nullptr;//确定删除节点while (cur) {if (key < cur->_kv.first) {parent = cur;cur = cur->_left;}else if (key > cur->_kv.first) {parent = cur;cur = cur->_right;}else//遇到了待删除的节点{if (cur->_left == nullptr)//如果待删除节点的左子树为空{if (cur == _root)//如果待删除节点是根节点{_root = cur->_right;if (_root) {_root->_parent = nullptr;}delete cur;return true;}else{deleteparentPos = parent;deletePos = cur;break;//有祖先节点说明要更新平衡因子}}else if (cur->_right == nullptr)//如果待删除节点的右子树为空{if (cur == _root)//如果待删除节点是根节点{_root = cur->_left;if (_root) {_root->_parent = nullptr;}delete cur;return true;}else{deleteparentPos = parent;deletePos = cur;break;//有祖先节点说明要更新平衡因子}}else//如果待删除节点的左右子树都不为空,替换法删除{//替换法删除//寻找待删除节点右子树中key值最小的节点作为实际删除节点(转化为左子树为空的局面)Node* minparent = cur;Node* minright = cur->_right;while (minright->_left){minparent = minright;minright = minright->_left;}//将待删除节点的属性更新为右子树中key值最小节点的属性cur->_kv.first = minright->_kv.first;cur->_kv.second = minright->_kv.second;//标记右子树中key值最小的节点为删除节点deleteparentPos = minparent;deletePos = minright;break;}}}//如果deletePos没有更新,说明没有找到根节点if (deletePos == nullptr){return false;}//记录待删除节点及其父节点(实际删除时要用)cur->_kv.first = minright->_kv.first;Node* del = deletePos;Node* delparent = deleteparentPos;//更新平衡因子while (deletePos != _root)//可能要一直更新到根节点{//判断删除的是父亲的哪一边,然后更新平衡因子if (deletePos = deleteparentPos->_left) {deleteparentPos->_bf++;}else{deleteparentPos->_bf--;}//判断是否需要往上继续更新if (abs(deleteparentPos->_bf) == 1) {break;}else if (deleteparentPos->_bf == 0) //其他两种情况树的高度都会变化所以要继续往上跟新平衡因子{deletePos = deleteparentPos;deleteparentPos = deleteparentPos->_parent;}else if (abs(deleteparentPos->_bf) == 2)//需要进行旋转处理{//判断旋转情况if (deleteparentPos->_bf = -2){if (deleteparentPos->_left->_bf == -1) {Node* tmp = deleteparentPos->_left;//记录右转后新的根节点rotateR(deleteparentPos);deleteparentPos = tmp;//旋转后根节点更新}else if(deleteparentPos->_left->_bf == 1) {Node* tmp = deleteparentPos->_left->_right;//记录右转后新的根节点rotateLR(deleteparentPos);deleteparentPos = tmp;//旋转后根节点更新}else//deleteparentPos->_left->_bf = 0{Node* tmp = deleteparentPos->_left;rotateR(deleteparentPos);deleteparentPos = tmp;deleteparentPos->_bf = 1;deleteparentPos->_right->_bf = -1;break;}}else//deleteparentPos->_bf = 2{if (deleteparentPos->_right->_bf == 1){Node* tmp = deleteparentPos->_right->_left;rotateRL(deleteparentPos);deleteparentPos = tmp;}else if (deleteparentPos->_right->_bf == -1){Node* tmp = deleteparentPos->_right;rotateL(deleteparentPos);deleteparentPos = tmp;}else{Node* tmp = deleteparentPos->_right;rotateL(deleteparentPos);deleteparentPos = tmp;deleteparentPos->_bf = -1;deleteparentPos->_left->_bf = 1;break;}}}else {assert(false);}//delParentPos树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子deletePos = deleteparentPos;deleteparentPos = deleteparentPos->_parent;}if (del->_left == nullptr) {if (del = delparent->_left) {delparent->_left = del->_right;if (del->_right) {del->_right->_parent = delparent;}}else {delparent->_right = del->_right;if (del->_right) {del->_right->_parent = delparent;}}}else{if (del == delparent->_left) {delparent->_left = del->_left;if (del->_left) {del->_left->_parent = delparent;}}else {delparent->_right = del->_left;if (del->_left) {del->_left->_parent = delparent;}}}delete del;return true;}

AVL树的查找

Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key) // key值大于该结点的值{cur = cur->_left; // 在该结点的右子树当中查找}else if (cur->_kv.first < key) // key值小于该结点的值{cur = cur->_right; // 在该结点的左子树当中查找}else // 当前节点的值等于key值{return cur; //返回该结点}}return nullptr; //查找失败}

AVL树的高度

采用了分治的思想

private:
void _Height(Node* root){if (!root) {return 0;}int left = _Height(root->_left);int right = _Height(root->_right);return left > right ? left + 1 : right + 1;}
public:
void Height(){_Height(_root);}

AVL树的判定

	bool _IsBalancedTree(Node* root) {if (!root) {return true;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;if (abs(diff) > 2) {cout << "wrong" << endl;return false;}else if (diff != root->_bf) {return  false;}return _IsBalancedTree(root->_left) && _IsBalancedTree(root->_right);}

AVL树的遍历

void _Inorder(Node* root){if (!root) {return;}_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}

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

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

相关文章

原生微信小程序 动态(横向,纵向)公告(广告)栏

先看一下动态效果 Y轴滚动公告的原理是swiper组件在页面中的Y轴滚动&#xff0c;属性vertical&#xff0c;其余属性也设置一下autoplay circular interval"3000" X轴滚动的原理是&#xff0c;利用动画效果&#xff0c;将内容从右往左过渡过去 wxml&#xff1a; &l…

在CSS中,盒模型中的padding、border、margin是什么意思?

在CSS中&#xff0c;盒模型&#xff08;Box Model&#xff09;是用来描述和布局HTML元素的基本概念。它将每个HTML元素看作是一个矩形的盒子&#xff0c;这个盒子包括了内容&#xff08;content&#xff09;、内边距&#xff08;padding&#xff09;、边框&#xff08;border&a…

Python使用 YOLO_NAS_S 模型进行目标检测并保存预测到的主体图片

一、前言&#xff1a; 使用 YOLO_NAS_S 模型进行目标检测&#xff0c;并保存预测到的主体图片 安装包&#xff1a; pip install super_gradients pip install omegaconf pip install hydra-core pip install boto3 pip install stringcase pip install typing-extensions pi…

外部库/lib/maven依赖项 三者关系

外部库(存放项目初始配置的jar包)(它的文件夹里并没有包含lib文件夹的引的外部的依赖的jar包) lib(存放外部导入到项目的依赖的jar包) maven依赖项(管理项目所有的jar包依赖) 三者存放jar包的关系 项目所依赖的全部的jar包 maven依赖项的jar包 外部库中的jar包 lib中的…

指针进阶详解

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.字符指针 2.指针数组 3.数组指针 4.数组传…

HTTP 框架修炼之道 | 青训营

Powered by:NEFU AB-IN 文章目录 HTTP 框架修炼之道 | 青训营 走进 HTTP 协议HTTP 框架的设计与实现应用层中间件层路由设计协议层 传输层&#xff08;网络层&#xff09;1. BIO&#xff08;Blocking I/O&#xff09;:2. NIO&#xff08;Non-blocking I/O&#xff09;:区别&…

跳跃游戏 II【贪心算法】

跳跃游戏 II class Solution {public int jump(int[] nums) {int cur 0;//当前最大覆盖路径int next 0;//下一步的最大覆盖路径int res 0;//存放结果&#xff0c;到达终点时最少的跳跃步数for (int i 0; i < nums.length; i) {//遍历数组&#xff0c;以给出数组以一个…

第七周第七天学习总结 | MySQL入门及练习学习第二天

实操练习&#xff1a; 1.创建一个名为 cesh的数据库 2.在这个数据库内 创建一个名为 xinxi 的表要求该表可以包含&#xff1a;编号&#xff0c;姓名&#xff0c;备注的信息 3.为 ceshi 表 添加数据 4.为xinxi 表的数据设置中文别名 5.查询 在 xinxi 表中编号 为2 的全部…

网络编程嵌套字

网络编程 程序员主要操作应用层和传输层来实现网络编程 也就是自己写一个程序&#xff0c;让这个程序可以使用网络来通信 这个程序属于应用层&#xff0c;实现通讯就需要获取到传输层提供的服务 这就需要使用传输层提供的api UDP&#xff1a;无连接&#xff0c;不可靠传输&a…

css background实现四角边框

2023.8.27今天我学习了如何使用css制作一个四角边框&#xff0c;效果如下&#xff1a; .style{background: linear-gradient(#33cdfa, #33cdfa) left top,linear-gradient(#33cdfa, #33cdfa) left top,linear-gradient(#33cdfa, #33cdfa) right top,linear-gradient(#33cdfa, #…

Viobot基本功能使用及介绍

设备拿到手当然是要先试一下效果的&#xff0c;这部分可以参考本专栏的第一篇 Viobot开机指南。 接下来我们就从UI开始熟悉这个产品吧&#xff01; 1.状态 设备上电会自动运行它的程序&#xff0c;开启了一个服务器&#xff0c;上位机通过连接这个服务器连接到设备&#xff0c…

无涯教程-分类算法 - 多项式逻辑回归模型函数

Logistic逻辑回归的另一种有用形式是多项式Lo​​gistic回归&#xff0c;其中目标或因变量可以具有3种或更多可能的unordered类型&#xff0c;即没有定量意义的类型。 用Python实现 现在&#xff0c;无涯教程将在Python中实现上述多项式逻辑回归的概念。为此&#xff0c;使用…

学习Linux基础知识与命令行操作

开始学习Linux系统前&#xff0c;首先要掌握计算机基础知识&#xff0c;了解硬件、操作系统、文件系统、网络和安全等概念。对这些基础知识的了解能够帮助理解Linux系统的概念和功能。 在Linux系统中&#xff0c;文件和目录是数据管理的基本单位。每个文件和目录都有一个称为&…

网络防御和入侵检测

网络防御和入侵检测是维护网络安全的关键任务&#xff0c;可以帮助识别和阻止未经授权的访问和恶意行为。以下是一些基本的步骤和方法&#xff0c;用于进行网络防御和入侵检测。 网络防御&#xff1a; 防火墙设置&#xff1a; 部署防火墙来监控和控制网络流量&#xff0c;阻止…

macOS上开源免费的新闻阅读器SABnzbd

SABnzbd Mac版是一款运行在Mac平台上的开源新闻阅读器&#xff0c;这款阅读器界面简约、功效简单强大&#xff0c;使用SABnzbd时可以帮助使用Python语言编写&#xff0c;让用户使用usenet新闻组更便利&#xff0c;是你阅读新闻的好帮手&#xff01; SABnzbd具有以下主要特点&a…

Linux枚举文件目录、获取文件属性

目录 1.枚举指定路径下的文件目录2.获取文件属性stat其他方式&#xff1a;Linux获取文件属性stat()、fstat()、lstat()函数实现stat属性代码 1.枚举指定路径下的文件目录 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.…

【Java架构-版本控制】-Git基础

本文摘要 Git作为版本控制工具&#xff0c;使用非常广泛&#xff0c;在此咱们由浅入深&#xff0c;分三篇文章&#xff08;Git基础、Git进阶、Gitlab搭那家&#xff09;来深入学习Git 文章目录 本文摘要1.Git仓库基本概念1.1 远程仓库(Remote)1.2 本地库(Repository) 2. Git仓库…

PMAC与Modbus主站进行Modbus Tcp通讯

PMAC与Modbus主站进行Modbus Tcp通讯 创建modbus通讯参数 在项目的PMAC Script Language\Global Includes下创建一个名为00_Modbus_Para.pmh的pmh文件。 Modbus[0].Config.ServerPort 0 Modbus[0].Config.ConnectTimeOut 6000 Modbus[0].Config.SendRecvTimeOut 0 Modbu…

基于Visual studio创建API项目

API&#xff08;英文全称&#xff1a;Application Programming Interface,中文&#xff1a;应用程序编程接口&#xff09; 为什么要 通过API接口可以与其他软件实现数据相互通信&#xff0c;API这项技术能够提高开发效率。 本文是基于vs2017 .net平台搭建API。希望可以帮助到学…

HBase--技术文档--基本概念--《快速扫盲》

官网 Apache HBase – Apache HBase™ Home 阿里云hbase 云数据库HBase_大数据存储_订单风控_数据库-阿里云 云数据库 HBase-阿里云帮助中心 基本概念 HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。它基于Hadoop&#xff0c;采用列式存储方式&#xff0c;可…