AVL树和红黑树
AVL树和红黑树
- AVL树
- 理论
- 代码实现
- 红黑树
- 理论
- 代码实现
AVL树
理论
我们知道二叉搜索树拥有极高的搜索效率,但当二叉搜索树退化成单支时,其查找效率会大幅下降,因此我们需要避免其出现单支的情况,并且尽可能让其接近满二叉树。解决办法是每次插入一个新结点时,都要保证所有结点的左右子树高度差都不能超过1,倘若新结点插入导致某颗子树左右子树的高度差超过1,就需要进行旋转处理,这样处理后的树称之为AVL树,可以保证其高度不会过高,其查找的时间复杂度的数量级为log(n)。
我们需要在每一个结点中增加一个变量bf以记录该结点的左右子树高度差(称之为平衡因子,一般是用右子树高度减去左子树高度的差作为平衡因子,AVL树中其值只能为-1,0,1)。当我们插入的新结点时,需要对从插入结点到根结点路径上的平衡因子进行调整,调整方法为:左子树高度增加,则当前结点平衡因子减1,右子树高度增加,则当前结点平衡因子加1,接着对其父结点也进行以上操作,直至根结点或者某个祖先结点的平衡因子变为0为止(当前结点平衡因子变为0说明新结点的插入并不影响以当前结点为根的子树的高度,那么自然就不会影响其到根结点路径上的祖先结点的平衡因子)。
倘若调整平衡因子过程中某个结点g的平衡因子变为了-2或者2,则需要进行旋转处理以合理调整树的高度,旋转的情况分为以下4种:
①g的左孩子为p,新插入结点c在p的左子树中
这种情况需要进行以g为根结点进行右单旋,具体步骤为:先将g的左孩子指向改为子树h2,h2子树的根结点的父亲指向改为指向g(如果子树h2存在的话),接着将p的左孩子指向改为指向g,p的父亲指向改为指向g指向的父结点,最后将g的父亲结点指向g的孩子指向改为指向p(如果g的父亲结点存在的话),g的父亲指向改为指向p。
此时p的平衡因子必定为0,调整后以p为根结点的子树的高度与插入结点前以g为根结点的子树高度相比并无变化,因此从p结点到根结点路径上面的祖先结点的平衡因子不需要再进行调整。
②g的右孩子为p,新插入结点c在p的右子树中
这种情况我们需要以g为根结点进行左单旋,具体步骤为:先将g的右孩子指向改为指向子树h2,再将h2的根结点的父亲指向改为指向g(如果子树h2不为空),接着将p的左孩子指向改为指向g,p的父亲指向改为指向g指向的父亲结点,最后将的父结点指向g的孩子指向改为指向p,g的父亲指向改为指向p。
此时p的平衡因子也必定为0,调整后以p为根结点的子树的高度与插入结点前以g为根结点的子树高度相比也无变化,因此从p结点到根结点路径上面的祖先结点的平衡因子也不需要再进行调整。
③g的左孩子为p,新插入结点c在p的右子树中
这种情况我们需要进行左右双旋,具体做法为:
1.以p结点为根结点进行左单旋
2.以g结点为根结点进行右单旋
此时结点t的平衡因子也必定为0,调整后以p为根结点的子树的高度与插入结点前以g为根结点的子树高度相比并无变化,因此从p结点到根结点路径上面的祖先结点的平衡因子不需要再进行调整。
④g的右孩子为p,新插入结点c在p的左子树中
这种情况我们需要进行右左双旋,具体做法为:
1.以p结点为根结点进行右单旋
2.以g结点为根结点进行左单旋
此时结点t的平衡因子也必定为0,调整后以p为根结点的子树的高度与插入结点前以g为根结点的子树高度相比并无变化,因此从p结点到根结点路径上面的祖先结点的平衡因子不需要再进行调整。
AVL树结点的删除我们不进行讨论,可以知道其结点的删除最坏情况下需要进行树的高度次的旋转,因此AVL树适用于数据是静态的情况,如果结构需要经常修改,AVL树就不太适用了。
代码实现
template<class T>
struct AVLTreeNode
{AVLTreeNode(const T& data = T()): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _bf(0){}AVLTreeNode<T>* _left;AVLTreeNode<T>* _right;AVLTreeNode<T>* _parent;T _data;int _bf; // 节点的平衡因子
};template<class T>
class AVLTree
{typedef AVLTreeNode<T> Node;
public:AVLTree(): _root(nullptr){}// 在AVL树中插入值为data的节点bool Insert(const T& data){Node* cur = new Node(data);if (nullptr == _root){_root = cur;return true;}Node* tmp = _root;Node* parent = nullptr;//插入while (tmp){parent = tmp;if (cur->_data > tmp->_data){tmp = tmp->_right;}else if (cur->_data < tmp->_data){tmp = tmp->_left;}else{return false;}}if (cur->_data > parent->_data){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}//调整平衡因子parent = cur;Node* grandpa = parent->_parent;while (grandpa){if (parent == grandpa->_left){--grandpa->_bf;}else{++grandpa->_bf;}if (2 == grandpa->_bf || -2 == grandpa->_bf)//旋转{if (parent == grandpa->_left && cur == parent->_left){//右单旋RotateR(grandpa);}else if (parent == grandpa->_right && cur == parent->_right){//左单旋RotateL(grandpa);}else if (parent == grandpa->_left && cur == parent->_right){//左右双旋RotateLR(grandpa);}else if (parent == grandpa->_right && cur == parent->_left){//右左双旋RotateRL(grandpa);}break;}else if (0 == grandpa->_bf){break;}cur = parent;parent = grandpa;grandpa = grandpa->_parent;}return true;}private:// 右单旋void RotateR(Node* parent){Node* left_child = parent->_left;parent->_left = left_child->_right;if (left_child->_right){left_child->_right->_parent = parent;}left_child->_right = parent;left_child->_parent = parent->_parent;if (parent->_parent){if (parent->_parent->_left == parent){//parent为其父结点的左孩子parent->_parent->_left = left_child;}else{//parent为其父结点的右孩子parent->_parent->_right = left_child;}}parent->_parent = left_child;if (_root == parent){_root = left_child;}//调整平衡因子left_child->_bf = 0;parent->_bf = 0;}// 左单旋void RotateL(Node* parent){Node* right_child = parent->_right;parent->_right = right_child->_left;if (right_child->_left){right_child->_left->_parent = parent;}right_child->_left = parent;right_child->_parent = parent->_parent;if (parent->_parent){if (parent->_parent->_left == parent){//parent为其父结点的左孩子parent->_parent->_left = right_child;}else{//parent为其父结点的右孩子parent->_parent->_right = right_child;}}parent->_parent = right_child;if (_root == parent){_root = right_child;}//调整平衡因子right_child->_bf = 0;parent->_bf = 0;}// 左右双旋void RotateLR(Node* parent){int pLR_bf = parent->_left->_right->_bf;RotateL(parent->_left);RotateR(parent);//调整平衡因子if (1 == pLR_bf){parent->_bf = 0;parent->_parent->_bf = 0;parent->_parent->_left->_bf = -1;}else if (-1 == pLR_bf){parent->_bf = 1;parent->_parent->_bf = 0;parent->_parent->_left->_bf = 0;}else if (0 == pLR_bf){parent->_bf = 0;parent->_parent->_bf = 0;parent->_parent->_left->_bf = 0;}}// 右左双旋void RotateRL(Node* parent){int pLR_bf = parent->_right->_left->_bf;RotateR(parent->_right);RotateL(parent);//调整平衡因子if (1 == pLR_bf){parent->_bf = -1;parent->_parent->_bf = 0;parent->_parent->_right->_bf = 0;}else if (-1 == pLR_bf){parent->_bf = 0;parent->_parent->_bf = 0;parent->_parent->_right->_bf = 1;}else if (0 == pLR_bf){parent->_bf = 0;parent->_parent->_bf = 0;parent->_parent->_right->_bf = 0;}}private:Node* _root;
};
红黑树
理论
红黑树通过颜色来控制树的高度,可以保证树的最大高度不会超过最小高度的2倍,其结点的颜色由以下规则来约束:
1.每个结点不是红色就是黑色
2.空节点可以看做是黑色结点
3.根结点是黑色的
4.一条路径上不能存在连续的红色结点(即如果当前结点是红色结点,那么其父亲结点和左右孩子结点必定是黑色的)
5.从根结点到叶子结点的每条路径上的黑色结点个数相同
由以上规则可知,红黑树的最小高度路径是该路径上的所有结点都是黑色的,最大高度路径是该路径上黑色和红色结点交替出现,这样就可以保证树的最大高度不会超过最小高度的2倍。
因此,只要遵循以上规则,红黑树的高度就可以保证。
我们需要在每个结点中增加一个变量color记录当前结点的颜色,在进行插入时,新插入的结点的颜色是红色,如果新插入结点的父结点的颜色是黑色的,则红黑树不需要进行任何调整,否则就需要调整。用c表示当前结点,p表示c的父亲结点,g表示c的祖父结点,u表示c的叔叔结点,其需要进行调整情况可以粗分为以下2种(细分5种):
-
u存在且为红色结点
我们只需要将p结点和u结点改为黑色,g改为红色,那么就可保证以g为根结点的子树必定遵循以上规则,接着将g赋给c,然后继续往上更新调整,直至不再出现连续红色结点或到根结点为止,如果更新到了根结点,我们最后必须让根结点变为黑色。
-
u为空结点或u为黑色结点
①c是p的左孩子,p是g左孩子
我们只需要以g为根结点进行一次右单旋,然后将p结点改成黑色,g结点改成红色,就一定可以保证整棵树依旧是红黑树,不必再往上调整。
②c是p的右孩子,p是g右孩子
我们只需要以g为根结点进行一次左单旋,然后将p结点改成黑色,g结点改成红色,就一定可以保证整棵树依旧是红黑树,不必再往上调整。
③c是p的右孩子,p是g左孩子
我们只需要以p为根结点进行一次左单旋,然后以g为根结点进行一次右单旋,再将c结点改成黑色,g结点改成红色,就一定可以保证整棵树依旧是红黑树,不必再往上调整。④c是p的左孩子,p是g右孩子
我们只需要以p为根结点进行一次右单旋,然后以g为根结点进行一次左单旋,再将c结点改成黑色,g结点改成红色,就一定可以保证整棵树依旧是红黑树,不必再往上调整。
红黑树结点的删除这里也不进行讨论。尽管红黑树的高度比AVL树高,但其在查找过程中性能与AVL树在同一个数量级上,为log(n),且相比较于AVL树,红黑树降低了插入删除时旋转的次数,因此在经常增删的结构中红黑树的性能更优一点。
代码实现
enum Color
{red,black
};template<class T>
struct RBTreeNode
{RBTreeNode(const T& data = T()): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(red){}RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Color _color; // 节点颜色
};template<class T>
class RBTree
{typedef RBTreeNode<T> Node;
public:RBTree():_root(nullptr){}// 注意:为了简单起见,实现的红黑树不存储重复性元素bool Insert(const T& data){//节点插入Node* newNode = new Node(data);if (_root == nullptr){_root = newNode;return true;}Node* cur = _root;while (cur){if (data > cur->_data && cur->_right){cur = cur->_right;}else if (data < cur->_data && cur->_left){cur = cur->_left;}else if (data == cur->_data){return false;}else{break;}}if (data > cur->_data){cur->_right = newNode;}else{cur->_left = newNode;}newNode->_parent = cur;//节点调整Node* grandpa =cur->_parent;Node* parent = cur;cur = newNode;while (nullptr!=grandpa){if (black == parent->_color){//cur节点的父节点为黑色,无需调整return true;}Node* uncle = grandpa->_right;if (parent == uncle){uncle = grandpa->_left;}if (uncle && red == uncle->_color){//叔叔节点存在且为红色uncle->_color = black;parent->_color = black;grandpa->_color = red;cur = grandpa;if (cur == _root){//如若cur已经是根节点,要将其修改为黑色cur->_color = black;break;}}else{//叔叔节点不存在或者叔叔节点为黑色if (parent == grandpa->_left && cur == parent->_left){//右单旋RotateR(grandpa);}else if (parent == grandpa->_right && cur == parent->_right){//左单旋RotateL(grandpa);}else if (parent == grandpa->_left && cur == parent->_right){//左右双旋RotateLR(grandpa);}else if (parent == grandpa->_right && cur == parent->_left){//右左双旋RotateRL(grandpa);}else{cout << "节点插入出错" << endl;exit(-1);}break;}parent = cur->_parent;grandpa = parent->_parent;}}private:// 左单旋void RotateL(Node* grandpa){//调整节点颜色grandpa->_color = red;grandpa->_right->_color = black;//节点旋转Node* right_child = grandpa->_right;grandpa->_right = right_child->_left;if (right_child->_left){right_child->_left->_parent = grandpa;}right_child->_left = grandpa;right_child->_parent = grandpa->_parent;if (grandpa->_parent){if (grandpa->_parent->_left == grandpa){//parent为其父结点的左孩子grandpa->_parent->_left = right_child;}else{//parent为其父结点的右孩子grandpa->_parent->_right = right_child;}}grandpa->_parent = right_child;if (_root == grandpa){_root = right_child;}}// 右单旋void RotateR(Node* grandpa){//调整节点颜色grandpa->_color = red;grandpa->_left->_color = black;//旋转节点Node* left_child = grandpa->_left;grandpa->_left = left_child->_right;if (left_child->_right){left_child->_right->_parent = grandpa;}left_child->_right = grandpa;left_child->_parent = grandpa->_parent;if (grandpa->_parent){if (grandpa->_parent->_left == grandpa){//grandpa为其父结点的左孩子grandpa->_parent->_left = left_child;}else{//grandpa为其父结点的右孩子grandpa->_parent->_right = left_child;}}grandpa->_parent = left_child;if (_root == grandpa){_root = left_child;}}//左右双旋void RotateLR(Node* grandpa){//调整节点颜色grandpa->_color = red;grandpa->_left->_right->_color = black;//旋转RotateL(grandpa->_left);RotateR(grandpa);}//右左双旋void RotateRL(Node* grandpa){//调整节点颜色grandpa->_color = red;grandpa->_right->_left->_color = black;//旋转RotateR(grandpa->_right);RotateL(grandpa);}private:Node* _root;
};
相关文章:

AVL树和红黑树
AVL树和红黑树 AVL树理论代码实现 红黑树理论代码实现 AVL树 理论 我们知道二叉搜索树拥有极高的搜索效率,但当二叉搜索树退化成单支时,其查找效率会大幅下降,因此我们需要避免其出现单支的情况,并且尽可能让其接近满二叉树。解…...

多线程入门
多线程 Thread 现在的Thread中的run方法,已经被实现了,所以已经不需要实现了 操作 继承 extends Thread方法 重写run方法 start() 案例 public class ThreadTest extends Thread{public void run() {for (int i 0; i < 100; i) {System.out.…...
#!/bin/sh和#!/bin/bash的区别
前言:都是脚本文件中的 shebang(也称为 hashbang)行,用于指定脚本文件的解释器 解释: #!/bin/sh:这行告诉操作系统使用 /bin/sh 这个解释器来执行脚本。/bin/sh 是一个标准的 Unix Shell,通常是…...

腾讯云(CVM)托管进行权限维持
前言 刚好看到一个师傅分享了一个阿里云ECS实战攻防,然后想到了同样利用腾讯云CVM的托管亦可实现在实战攻防中的权限维持。 简介 腾讯云自动化助手(TencentCloud Automation Tools,TAT)是一个原生运维部署工具,它可…...

STM32-03基于HAL库(CubeMX+MDK+Proteus)输入检测案例(按键控制LED)
文章目录 一、功能需求分析二、Proteus绘制电路原理图三、STMCubeMX 配置引脚及模式,生成代码四、MDK打开生成项目,编写HAL库的按键检测代码五、运行仿真程序,调试代码 一、功能需求分析 搭建完成开发STM32开发环境之后,开始GPIO…...
DS3231SN
这份文件是关于DS3231SN芯片的数据手册,由Maxim Integrated公司生产。DS3231SN是一款高精度的I2C接口集成实时时钟(RTC)/温度补偿晶体振荡器(TCXO)/晶体的芯片。以下是该芯片的核心内容概述: 产品概述&…...
tsconfig.json文件翻译
原文件 {"compilerOptions": {/* Visit https://aka.ms/tsconfig to read more about this file *//* Projects */// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects.…...
树状数组学习笔记
树状数组 拜读了大佬的讲解博文(树状数组(详细分析应用),看不懂打死我!),写一篇Python版的笔记巩固消化,附带蓝桥杯历年真题作为例题演示 一、作用 用于快速读取列表中 某个区间内所有元素的和 实现单点修改ÿ…...
【bugfix】如何解决svg到线上显示空白或者svg的viewBox为空
svgo的默认机制是当width和height和viewbox一样会删除viewbox,这都是为了svg的压缩做的,详情可以看issue中的讨论,我们可以通过更改babel的配置来解决 https://github.com/svg/svgo/issues/1128 https://github.com/ant-design/ant-design-we…...

docker基础学习指令
文章目录 [toc] docker基础常用指令一、docker 基础命令二、docker 镜像命令1. docker images2. docker search3. docker pull4. docker system df5. docker rmi1. Commit 命令 三、 docker 容器命令1. docker run2. docker logs3. docker top4. docker inspect5. docker cp6. …...
回溯大学生活
回顾一下大学四年 bg:湖南大学 20级计科,成绩60%,无考研考公打算 四年之前,怀着激动的心情来到了大学校园,经过了太久的压抑终于迎来了高中老师口中的美好的大学生活,然而呢事实并非如此。恋爱呢…...
Android Fence机制
Android Fence机制 Android中的GraphicBuffer同步机制-Fence (最全最详细,推荐) AndroidQ 图形系统(5)Fence机制简介 Android P 图形显示系统(十一) BufferQueue(二)...

sa-token非Web上下文无法获取Request
0x02 非Web上下文无法获取Request 问题定位 在我们使用sa-token安全框架的时候,有时候会提示:**SaTokenException:非Web上下文无法获取Request**** 错误截图: 在官方网站中,查看常见问题排查: 非Web上下文无法获取…...
tomcat 常见优化方案
tomcat作为Web服务器,它的处理性能直接关系到用户体验,下面是几种常见的优化措施: 对web.xml的监视,把jsp提前编辑成Servlet。有富余物理内存的情况,加大tomcat使用的jvm的内存 服务器所能提供CPU、内存、硬盘的性能…...
前端导出文本内容为csv文件,excel乱码
原因:编码格式问题,需要改为utf-8 bom // Create blob with utf8-bom 编码 const createBlobWithBOM(data, mimeType)> {const bom [0xEF, 0xBB, 0xBF];const bomArray new Uint8Array(bom);const dataArray new TextEncoder().encode(data);const…...

36---USB HUB电路设计
视频链接 USB HUB电路设计01_哔哩哔哩_bilibili USB HUB 电路设计 1、USB HUB基本介绍 USB Hub,指的是一种可以将一个USB接口扩展为多个,并可以使这些接口同时使用的装置。 Hub也是大家常说的集线器,它使用星型拓扑结构连接多个USB接口设…...
FPGA在深度学习领域的应用的优势
FPGA(Field-Programmable Gate Array)是一种可编程逻辑芯片,可以根据需要重新配置其内部的逻辑电路和功能。在深度学习领域,FPGA被广泛用于加速模型训练和推理任务。 首先,FPGA可以提供高度定制化的计算架构ÿ…...
Windows Edge 兼容性问题修复 基本解决方案
Windows Edge 浏览器兼容性问题可能源于多个方面,以下是一些常见的问题及其处理结果: 插件或扩展冲突:某些第三方插件或扩展可能与Edge浏览器不兼容,导致崩溃或运行异常。处理结果为,尝试禁用所有插件和扩展ÿ…...

【Servlet】服务器内部转发以及客户端重定向
文章目录 一、服务器内部转发:request.getRequestDispatcher("...").forward(request, response);二、客户端重定向:response.sendRedirect("");三、服务器内部转发代码示例四、客户端重定向代码示例 一、服务器内部转发:…...

是否有替代U盘,可安全交换的医院文件摆渡方案?
医院内部网络存储着大量的敏感医疗数据,包括患者的个人信息、病历记录、诊断结果等。网络隔离可以有效防止未经授权的访问和数据泄露,确保这些敏感信息的安全。随着法律法规的不断完善,如《网络安全法》、《个人信息保护法》等,医…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...