深入剖析红黑树:优雅地平衡二叉搜索树

news2024/11/15 5:02:15

在这里插入图片描述

目录

  • 一.红黑树的概念
    • 二.插入操作
      • 三.与AVL树的比较

一.红黑树的概念

在之前的学习中,我们了解了二叉搜索平衡树,AVL树通过控制每个结点中的平衡因子的绝对值不超过1,实现了一个高性能的树。而相较于AVL的高度平衡,红黑树觉得AVL为了平衡也付出了代价(插入和删除时进行了多次旋转),所以红黑树在控制平衡上面没有这么严格,只是要求,最长路径不超过最短路径的二倍。那红黑树又是如何控制实现的呢?接下来了解一下红黑树的性质:

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的(任何路径上没有连续两个红结点)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,也称为NIL结点

二.插入操作

在我们了解红黑树的性质后,就需要分析相关代码看他如何实现的。首先我们看红黑树结点的定义:
因为map和set的底层使用红黑树实现了,为了之后方便,这里红黑树用了两个模板参数。

#include <iostream>

using namespace std;

enum Colour
{
	RED, BLACK
};

template<class K,class V>
class RBTreeNode
{
public:
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	Colour _col;

	RBTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}
};

结点定义中与AVL树差距不大,只是多了个用枚举定义的参数,用来指定是红结点还是黑结点。接下来讲解重点的插入操作:
首先因为红黑树也是二叉搜索树,所以要满足二叉搜索树的基本性质,再者是我们插入的结点的颜色先置为什么能让后面的调整更方便呢。如果黑色需要在后面依据性质4调整,插入红色的话依据性质3调整。明显是4更为复杂,所以我们插入颜色为红色

bool Insert(const pair<K, V>& kv)
{
	if(_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		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);
	cur->_col = RED;

	if (parent->_kv.first > cur->_kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;
	///开始调整颜色
	///开始调整颜色
	_root->_col = BLACK;
	return true;
}

上段代码是不涉及调整颜色,只保证二叉搜索树性质。下面开始分类讨论研究如何调整颜色。
在这里插入图片描述

如上图所示,插入的cur结点是红色,这时出现了连续的两个红结点,所以就要进行调整。如果parent结点是黑色则不需要调整。我们把10结点和20结点称为parent和grandfather结点,22为uncle结点
1.当uncle结点为红色时,变色然后继续向上调整
在这里插入图片描述
2.当uncle结点不存在时或者为黑时的处理方式相同:
在这里插入图片描述
完整代码如下:

	bool Insert(const pair<K, V>& kv)
	{
		if(_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			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);
		cur->_col = RED;

		if (parent->_kv.first > cur->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;//叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在或者为黑都是旋转+变色
				{
					if (cur == parent->_left)
					{
						RevoR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RevoL(parent);
						RevoR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
			}
			else
			{
				Node* uncle = grandfather->_left;//叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else // 叔叔不存在或者为黑都是旋转+变色
				{
					if (cur == parent->_left)
					{
						RevoR(parent);
						RevoL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						RevoL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
				}
			}
		}

		_root->_col = BLACK;
		return true;
	}
	void RevoL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		parent->_right = curleft;//无论curleft是否为空都要执行这一步

		if (curleft)
		{
			curleft->_parent = parent;
		}
		cur->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;

		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;

			}
			cur->_parent = ppnode;
		}

	}

	void RevoR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		parent->_left = curright;

		if (curright)
		{
			curright->_parent = parent;
		}
		cur->_right = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;
		if (_root == parent)//等价于 ppnode == nullptr
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			cur->_parent = ppnode;
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}
		}
	}

三.与AVL树的比较

红黑树和AVL树的插入效率O(logN),只是红黑树不像AVL追求如此平衡,所以旋转次数会少,并且实现也较简单。所以在实践中大都使用红黑树。之后我们还是使用红黑树模拟实现map和set

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

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

相关文章

Java 基于 SpringBoot+Vue 的留守儿童关爱网站

文章目录 1.研究背景2. 技术栈3.系统分析4系统设计5系统的详细设计与实现5.1系统功能模块5.2管理员功能模块 源码下载地址 1.研究背景 以往的留守儿童爱心的管理&#xff0c;一般都是纸质文件来管理留守儿童爱心信息&#xff0c;传统的管理方式已经无法满足现代人们的需求&…

《存储IO路径》-进程、线程和任务的区别

在Linux IO系统架构中&#xff0c;我们经常会听到进程、线程、任务、jbobs&#xff0c;QD&#xff0c;这些词汇常常会有一些模糊和混淆&#xff0c;本文我们主要对比下这几个概念的区别。 我们来看看这些术语在Linux IO路径中的角色&#xff1a; Process&#xff08;进程&…

基于STM32 ZigBee无线远程火灾报警监控系统物联网温度烟雾

实践制作DIY- GC00168---ZigBee无线远程监控系统 一、功能说明&#xff1a; 基于STM32单片机设计---ZigBee无线远程监控系统 二、功能说明&#xff1a; 1个主机&#xff1a;STM32F103C系列单片机LCD1602显示器蜂鸣器 ZigBee无线模块3个按键&#xff08;设置、加、减&#xff0…

计算机竞赛 题目:基于FP-Growth的新闻挖掘算法系统的设计与实现

文章目录 0 前言1 项目背景2 算法架构3 FP-Growth算法原理3.1 FP树3.2 算法过程3.3 算法实现3.3.1 构建FP树 3.4 从FP树中挖掘频繁项集 4 系统设计展示5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于FP-Growth的新闻挖掘算法系统的设计与实现…

Gallery2分析

本文分析的代码基于 Android 4.x 文章目录 Gallery2介绍基本数据数据源DataManager数据管理LocalSource本地数据源Media数据的加载过程Gallery2页面结构ActivityStateStateManagerGalleryAppImplAbstractGalleryActivityGalleryActivityAlbumSetPage数据加载和渲染过程分析第四…

Python操作MongoDb创建文档及CRUD基本操作

Python3中类的高级语法及实战 Python3(基础|高级)语法实战(|多线程|多进程|线程池|进程池技术)|多线程安全问题解决方案 Python3数据科学包系列(一):数据分析实战 Python3数据科学包系列(二):数据分析实战 Python3数据科学包系列(三):数据分析实战 MongoDB 操作手册----文档…

Access注入---偏移注入 | Mysql注入---DNS注入 | MSSQL---反弹注入

伪静态---假的静态页面判断页面是否为静态&#xff1a;document.lastModified偏移注入使用场景&#xff1a;遇到知道表明&#xff0c;但不知道字段名的情况下使用表.* >&#xff08;核心&#xff09;information_schema.tables&#xff08;去information_schema库里面选中ta…

头戴式耳机怎么戴好看?头戴式耳机正确代法

走在大街上总能看到那么一些人&#xff0c;他们眼神时而朦胧涣散&#xff0c;时而精神奕奕&#xff0c;全身上下始终散发着#请勿打扰#的气息&#xff0c;因为他们都戴着头&#xff01;戴&#xff01;式&#xff01;耳&#xff01;机&#xff01;但是头戴式耳机把头压得扁扁的&a…

UG\NX CAM二次开发 创建加工环境 UF_SETUP_create

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 创建加工环境 UF_SETUP_create,我们ug编程也是要进去加工模块加载环境,选择模板的这是我们加工编程的第一步 代码实现进入加工环境: void MyClass::do_it() { // 创建加工设置 UF_SETUP…

1300*D. Alice, Bob and Candies(模拟)

Problem - 1352D - Codeforces 解析&#xff1a; 模拟即可。 #include<bits/stdc.h> using namespace std; #define int long long const int N2e55; int t,n,a[N]; signed main(){scanf("%lld",&t);while(t--){scanf("%lld",&n);for(int i…

XPD977协议系列-支持 XPD-LINK™互联 USB 三端口控制器

XPD977 是一款集成 USB Type-C、USB Power Delivery&#xff08;PD&#xff09; 3.0/2.0 以及 PPS、QC3.0/2.0CLASS B 快充协议、华为 FCP/SCP 快充协议、三星 AFC 快充协议、VOOC 2.0 快 充协议、BC1.2 DCP 以及苹果设备 2.4A 充电规范的多功能 USB 三端口控制器&#xff0c;为…

【LeetCode刷题笔记】二维数组

498.对角线遍历 解题思路&#xff1a; 简化问题&#xff0c;首先考虑按照逐条对角线打印元素&#xff0c;而不考虑翻转的情况。 M 行 N 列的二维矩阵总共有 M N - 1 条对角线&#xff08; 右上 -> 左下 &#xff09; 1&#xff09;如何遍历&#xff1a; 从左往右遍历 对角…

【小沐学C++】git和github常见问题汇总

文章目录 1、简介2、下载和安装2.1 Git2.2 TortoiseGit 3、相关功能3.1 基本命令3.2 更新子模块命令 4、常见问题4.1 GitHub访问慢或者无法访问4.1.1 修改本地hosts映射4.1.2 通过Gitee中转fork仓库下载 4.2 OpenSSL SSL_read: Connection was reset, errno 10054 结语 1、简介…

怎么将Linux上的文件上传到github上

文章目录 1. 先在window浏览器中创建一个存储项目的仓库2. 复制你的ssh下的地址1) 生成ssh密钥 : 在Linux虚拟机的终端中,运行以下命令生成ssh密钥2)将ssh密钥添加到github账号 : 运行以下命令来获取公钥内容:3. 克隆GitHub存储库:在Linux虚拟机的终端中,导航到您想要将文件上…

redis的持久化消息队列

Redis Stream Redis Stream 是 Redis 5.0 版本新增加的数据结构。 Redis Stream 主要用于消息队列&#xff08;MQ&#xff0c;Message Queue&#xff09;&#xff0c;Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能&#xff0c;但它有个缺点就是消息无法…

OOTD | 美式复古穿搭耳机,复古轻便的头戴式耳机推荐

复古耳机更能带来年代感的复古数码产品&#xff0c;头戴式耳机就好似是时光滤镜的时髦配饰&#xff0c;不说功能实用性&#xff0c;在造型上添加就很酷。 随着时代的发展&#xff0c;时尚有了新的定义。对如今的消费者来说&#xff0c;时尚不仅是美学与个性的展现&#xff0c;…

各种机器码的本质(原码、反码、补码、移码、IEEE754格式阶码)

总述 无论使用什么格式的机器码来表示真值&#xff0c;若取一定位数n以后&#xff0c;各个比特位的排列个数是一定的&#xff0c;为 2 n 2^n 2n种排列&#xff0c;所以选择什么格式的机器码实质上选择什么映射方式来完成从这 2 n 2^n 2n种离散排列到离散的整数真值的映射&…

Open X-Embodiment Robotic Learning Datasets and RT-X Models

文章目录 简介论文链接项目链接Reference 简介 为什么机器人技术远远落后于 NLP、视觉和其他 AI 领域&#xff1f;除其他困难外&#xff0c;数据短缺是罪魁祸首。谷歌 DeepMind 联合其他机构推出了 Open X-Embodiment 数据集&#xff0c;并训练出了能力更强的 RT-X 模型。 Dee…

6-1 选择排序

#include <stdio.h>#define N 1000 int arr[N];/* 对长度为n的数组arr执行选择排序 */ void selectionSort(int arr[], int n);/* 打印长度为n的数组arr */ void printArray(int arr[], int n);void swap(int *xp, int *yp) {int temp *xp;*xp *yp;*yp temp; }int mai…

第八章 排序 二、插入排序

目录 一、算法思想 二、例子 三、代码实现 四、空间复杂度 五、时间复杂度 1、最好的情况 2、最坏的情况 六、优化&#xff08;折半插入排序&#xff09; 七、总结 一、算法思想 每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中&#xff0c;直到所…