26 红黑树

news2024/11/25 0:25:45

目录

1.概念
2.性质
3.节点定义
4.结构
5.插入
6.验证
7.删除
8.红黑树和avl树比较
9.应用

概念

是一种二叉搜索树,但在每个节点上增加一个存储位表示节点的颜色,可以是red或black。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长处两倍,因此是接近平衡的

在这里插入图片描述

性质

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

第5条指的是空节点,方便数出二叉树的路径,上面的红黑树共有11条路径,每条路径都有2个黑色节点

最短路径:全黑
最长路径:一黑一红间隔
假设共有N个黑色节点,最短路径logN,最长路径2logN
假设每条路径都有N个黑色节点,每条路径的结点数量[N,2*N]之间

avl树是绝对平衡,红黑树是相对平横,最长路径不超过最短路径的2倍

节点定义

枚举一个颜色,红和黑,节点中加入颜色

enum color
{
	RED,
	BLACK
};

template <class K, class V>
struct TreeNode
{
	struct TreeNode<K, V>* _parent;
	struct TreeNode<K, V>* _left;
	struct TreeNode<K, V>* _right;
	std::pair<K, V> _kv;
	color _col;

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

为什么节点默认是红色?
因为每条路径的黑色节点数量都必须相等,如果插入结点默认为黑色,就会对所有路径造成影响。如果是红色,最多只会影响自己所在的路径

红黑树结构

为了后续实现关联式容器简单,红黑树的实现中可以增加一个头结点,因为根节点必须为黑色,为了与根节点区分,将头结点给成黑色,并且让头结点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的结点,_pRight域指向红黑树最大的结点,如下,本节的实现不采用头结点
在这里插入图片描述

插入

红黑树是在二叉搜索树的基础上加入其平衡限制条件,因此红黑树的插入可分为两步:

插入结点

#pragma once
#include <iostream>
#include <assert.h>
#include <queue>

enum color
{
	RED,
	BLACK
};

template <class K, class V>
struct TreeNode
{
	struct TreeNode<K, V>* _parent;
	struct TreeNode<K, V>* _left;
	struct TreeNode<K, V>* _right;
	std::pair<K, V> _kv;
	color _col;

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

template <class K, class V>
class RBTree
{
	typedef TreeNode<K, V> node;
public:

	bool insert(const std::pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new node(kv);
			_root->_col = BLACK;
			return true;
		}

		node* parent = nullptr;
		node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (kv.first < cur->_kv.first)
			{
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//插入
		cur = new node(kv);
		cur->_parent = parent;
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}	
	}

private:
	node* _root = nullptr;
};

检测新节点插入后,红黑树的性质是否遭到破坏

因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲颜色是红色时,就违反了性质三不能有连在一起的红色节点,此时分情况讨论:

约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

1.当a/b/c/d/e都是空,cur就是新增
在这里插入图片描述

2.c/d/e是每条路径1个黑色节点的红黑树,那么就是x/y/z/r中任意一种
在这里插入图片描述
cur更改,变为黑色节点,左右各连a和b节点,cur就是在a和b插入,就会失衡
在这里插入图片描述

cde共有444种可能,插入位置有4个,就是64*4=256种可能

3.当每条路径变为2个黑色节点的子树
在这里插入图片描述
在这里插入图片描述

第一个是C88,C87…C80,再加上下面两个,第一个2个红色节点是C42,可以换一边,就是C42*2,第二个是C44,如果这些是100种可能,c,d,e就有1003种可能,其他更不用算了

从上面所有肯能找出一种规律,解决插入的相关问题
如果插入结点的双亲是黑色,不需要处理,是红色时才需要下面内容

  • 情况一:cur为红,p为红,g为黑,u存在且为红
  • 注意:此处看到的树,可能是一颗完整的树,也可能是一颗子树
    在这里插入图片描述

上面的右边是左边变色而来,p和u变黑,g变红,就平衡了
如果g是根节点,调整完成后,需要改为黑色

如果g是子树的根,调整完成后,g的双亲如果是黑色,就要继续往上调整
在这里插入图片描述
解决方式,将p和u改为黑,g改为红,然后把g当做cur,继续向上调整

  • 情况二,cur为红,p为红,g为黑,u不存在/存在且为黑

在这里插入图片描述
u的情况有两种:
1.如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4
2.如果u节点存在,则一定是黑色的,那么cur原来的颜色也是黑色,现在看到的红色是因为cur的子树在调整的过程中将cur节点的颜色由黑色变为红色
在这里插入图片描述

在这里插入图片描述
p为g的左孩子,cur为p的左孩子,则进行右单旋转,相反
p为g的右孩子,cur为p的右孩子,则进行左单旋转

  • 情况三,cur为红,p为红,g为黑,u不存在/存在且为黑

和情况二类似
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转,旋转后变成了情况二,再右单旋转
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,旋转后变成了情况二,再左单旋转

在这里插入图片描述

调整

//父节点是红色调整
while (parent && parent->_col == RED)
{
	node* gdparent = parent->_parent;
	node* uncle;

	if (gdparent->_left == parent)
	{
		uncle = gdparent->_right;
		//第一种情况 叔叔节点是红色,变色
		if (uncle && uncle->_col == RED)
		{
			parent->_col = uncle->_col = BLACK;
			gdparent->_col = RED;
		}
		//第二种情况 分左右
		//     g
		//  par  un
		// cur
		else
		{
			if (cur == parent->_left)
			{
				RotateRight(gdparent);
				parent->_col = BLACK;
				gdparent->_col = RED;
			}
			//     g
			//  par  un
			//    cur
			else
			{
				RotateLeft(parent);
				RotateRight(gdparent);
				cur->_col = BLACK;
				parent->_col = RED;
				gdparent->_col = RED;
			}

			break;
		}
	}
	else
	{
		uncle = gdparent->_left;
		if (uncle && uncle->_col == RED)
		{
			parent->_col = uncle->_col = BLACK;
			gdparent->_col = RED;
		}
		else
		{
			if (cur == parent->_right)
			{
				RotateLeft(gdparent);
				parent->_col = BLACK;
				gdparent->_col = RED;
			}
			//     g
			//  par  un
			//    cur
			else
			{
				RotateRight(parent);
				RotateLeft(gdparent);
				cur->_col = BLACK;
				//parent->_col = RED;
				gdparent->_col = RED;
			}

			break;
		}
	}

	cur = gdparent;
	parent = cur->_parent;
}

//根必须是黑色
_root->_col = BLACK;
return true;

根节点在最后必须变为黑色,在函数最后部分统一改变
首先判断par在g的左边的三种情况,叔叔节点存在且是红色直接变色,更新节点向上。叔叔节点不存在或为黑色,判断cur在par的哪边,采取不同的旋转,然后改变节点颜色
par在g右边是相反

插入过程:
在这里插入图片描述

红黑树的验证

红黑树的验证分为两步:
1.检测是否满足二叉搜索树的(中序遍历是否有序序列)
2.检测是否满足红黑树的性质
先判断根节点是否为黑色,再记录一条路径黑色节点数量用来参考,递归红黑树,遇见黑色节点,黑色节点数量++。如果是红色节点,判断它的父节点是否为红色。遇到空,判断这条路径黑色节点数量和参考值是否一样

bool IsBalance()
{
	if (_root->_col == RED)
	{
		std::cout << "根节点是红色" << std::endl;
		return false;
	}

	int refVal = 0;  //记录最左路径黑色节点的数量,用来参考
	node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			refVal++;
		}

		cur = cur->_left;
	}

	return _IsBalance(_root, 0, refVal);
}

bool _IsBalance(node* cur, int blackNum, int refVal)
{
	if (cur == nullptr)
	{
		//判断每条路径黑色节点数量正常
		if (blackNum != refVal)
		{
			std::cout << "黑色节点数量不相等" << std::endl;
			return false;
		}
		return true;
	}
	
	if (cur->_col == BLACK)
	{
		blackNum++;
	}

	if (cur->_col == RED && cur->_parent->_col == RED)
	{
		std::cout << cur->_kv.first << " 连续红色节点" << std::endl;
	}

	return _IsBalance(cur->_left, blackNum, refVal) 
		&& _IsBalance(cur->_right, blackNum, refVal);
}

删除

参考《算法导论》或者《STL源码剖析》
https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html

这里只提供思路
删除前部分内容和avl树都一样,如果删除的节点有两个孩子,就找前驱节点替换,这样所有情况都变成了删除节点只有一个孩子的情况,如果没有孩子也可以当做只有一个孩子处理

父亲节点称为par,删除节点del,孩子节点为child

  • 第一种情况,删除的结点是红色,它如果有孩子一定是黑色,只需要将par链接到child
  • 第二种情况,删除节点是黑色,child为红色,还是将par连接到child,将child改为黑色
  • 第三种情况,删除节点是黑色,child也为黑色,这时考察del的兄弟节点,v有两种情况

情况1,v是黑色节点,v的左子女是w,根据w的颜色讨论:
下图g为祖父节点,u为del的child节点,v是del的兄弟节点,del节点在g的右孩子,删除后g连接到u
当删除了del时,g的右边ul和ur少了一个黑色节点,g的左边的每条路径wl,wr,vr都有相等的黑色节点

根据节点w的状态,有以下情况:
设平衡路径有2个黑色节点
1.w是红色节点,设wl,wr,vr都有2个黑色节点,wl,wr,vr中各有一个黑色节点,ul和ur无黑色节点。以g为中心右旋,vr接到g的左边,v变为红色,w和g改为黑色。此时,wl和wr里面各有1个黑色,w也是黑色,还是2个。vr中有1个黑色,加上g也是2个,u是黑色,g是黑色,ul和ur路径也是两个黑色节点

2.w是黑色节点,这时看w的有兄弟r节点,根据r分情况
① r是红色节点,通过一次先左后右的双旋,将g染成黑色
wl和wr无黑色节点,rl和rr各有1个黑色。旋转完后,右边增加一个黑色节点
② r是黑色节点,r是黑色节点,这时就要看g的颜色,如果g是红色,只要交换g和子女v的颜色。如果g是黑色,可以做一次右单旋转,将节点v上升染成双重黑色,消除节点u的双重黑色,双重黑色向根的方向移动
在这里插入图片描述
情况2,v是红色节点,考察有子女r,r一定是黑色节点,在看r的左子女s,根绝s的颜色分两种情况:
(1)s是红色,先左后右双旋,s染黑色,r上升,使包含u的路径的黑高度加1
在这里插入图片描述

(2) s是黑色,再看s的右兄弟节点t,根绝t的颜色分情况:
① 如果t为红色,先以t左旋,t替补r的位置,然后再以t先左后右的双旋,消除u的双重黑色
在这里插入图片描述

② 若节点t是黑色,以v为轴右单旋转,并改变v和r的颜色
在这里插入图片描述

节点u是g的左子女的情况是镜像的

红黑树和avl树的比较

都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2N log2N),红黑树不追求绝对平衡,只要保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以再经常增删的结构中更优,实际中红黑树用的更多

红黑树的应用

1.C++STL库,map/set、mutil_map/mutil_set
2.java库
3.linux内核
4.其他一些库

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

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

相关文章

【嵌入式】适配大多数STM32的最小系统设计

大多数的参赛作品以及毕业设计中都仅仅简单的使用STM32F103最小系统板加上洞洞板或面包板。这样的设计虽然可以进行功能演示&#xff0c;但是由于市面上的最小系统板太过简单&#xff0c;并没有发挥出STM32系列MCU的强大功能&#xff0c;而且看起来很不美观&#xff0c;为了在参…

排序——快速排序

目录 思想 演示 代码实现 解释 优化 三数取中 小区间优化 补充 挖坑法 双指针法 非递归实现 思想 快速排序是一种二叉树结构的交换排序方法。 基本思想为&#xff1a;任取待排序元素序列中的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&am…

从零开始理解 XML 和 JSON 的区别

在这篇文章中&#xff0c;我们将深入探讨XML和JSON这两种数据格式的关键异同点&#xff0c;以便读者可以根据项目需求做出明智的技术选择。 了解XML XML&#xff08;Extensible Markup Language&#xff09;是一种用于数据定义的标记语言&#xff0c;最初由万维网联盟&#x…

【调试笔记-20240618-Windows-pnpm 更新出现 Cannot find module 问题的解决方法】

调试笔记-系列文章目录 调试笔记-20240618-Windows-pnpm 更新出现 Cannot find module 问题的解决方法 文章目录 调试笔记-系列文章目录调试笔记-20240618-Windows-pnpm 更新出现 Cannot find module 问题的解决方法 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调…

烂笔头笔记:为JDK安装Charles证书,让你的请求能够像在浏览器中那样被抓包

为什么要为JDK安装Charles证书 众所周知&#xff0c;https就是为了防止中间过程被拦截从而导致数据泄密的。若强行加入Charles代理&#xff0c;数据被解密后再被其重新加密&#xff0c;数据已经被“破坏”&#xff0c;客户端从而拒绝建立连接或解析内容。 #mermaid-svg-ksLo5W…

零样本学习及应用

传统的图像分类任务只是期望模型可以预测未见过图像的类别&#xff0c;而零样本学习则旨在预测从未见过的类别。对于原始图像分类任务来说&#xff0c;为每个类别收集大量训练数据对于最终拥有一个稳健的模型非常重要。然而&#xff0c;要获得大量的标注数据并不总是那么容易。…

【Unity设计模式】观察者模式,发布订阅模式,事件总线

前言 最近在学习Unity游戏设计模式&#xff0c;看到两本比较适合入门的书&#xff0c;一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》 这两本书介绍了大部分会使用到的设计模式&#xff0c;因此很值得学习 本…

Redis小对象压缩

小对象压缩存储 如果Redis内部管理的集合数据结构很小&#xff0c;他会使用紧凑存储形式压缩存储。 Redis的ziplist是一个紧凑的字节数组结构&#xff0c;如下图所示&#xff0c;每个元素之间都是紧挨着的。 如果他存储的是hash结构&#xff0c;那么key和value会作为两个ent…

UE5 C++ 跑酷游戏练习 Part1

一.修改第三人称模板的 Charactor 1.随鼠标将四处看的功能的输入注释掉。 void ARunGANCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) {// Set up action bindingsif (UEnhancedInputComponent* EnhancedInputComponent CastChecked&…

微服务开发与实战Day11 - 微服务面试篇

一、分布式事务 1. CAP定理 1998年&#xff0c;加州大学的计算机科学及Eric Brewer提出&#xff0c;分布式系统有三个指标&#xff1a; Consistency&#xff08;一致性&#xff09;Availability&#xff08;可用性&#xff09;Partition tolerance&#xff08;分区容错性&am…

“Git之道:掌握常用命令,轻松管理代码“

目录 1. 初始化和配置 2. 提交和更新 3. 分支和合并 4. 查看和比较 5. 远程仓库 6. 文件操作命令 1. 初始化和配置 git init&#xff1a;在当前目录初始化一个新的Git仓库git config&#xff1a;配置Git的全局或局部选项git clone&#xff1a;从远程仓库克隆一个本地副本…

MySQL基础——函数和约束

目录 1函数 1.1字符串函数 1.2数值函数 1.3日期函数 1.4流程函数 2约束 2.1约束概述和演示 2.2外键约束&#xff08;表连接键&#xff09; 1函数 函数是指一段可以直接被另一段程序调用的程序或代码。 1.1字符串函数 MySQL中内置了很多字符串函数&#xff0c;常用的…

【C语言习题】31.冒泡排序

文章目录 作业标题作业内容2.解题思路3.具体代码 作业标题 冒泡排序 作业内容 实现一个对整形数组的冒泡排序 2.解题思路 先了解一下冒泡排序&#xff1a; 两两相邻的元素进行比较&#xff0c;如果前面元素大于后面元素就交换两个元素的位置&#xff0c;最终的结果是最大的…

三星S20以上手机中的动态相片及其分解

三星S20以后的相机&#xff0c;相机拍出来的图片&#xff0c;用三星手机自带的“相册”打开之后&#xff0c;还会有“查看动态照片”的选项&#xff0c;点击之后就能查看拍照片时前后2秒左右的视频&#xff01; 不知道这个功能是不是三星独有的。 这样得到的图片非常大。因为…

一个简单的信号发射电路的构建

在基本的信号发射电路中&#xff0c;线圈&#xff08;电感器&#xff09;和电阻的组合可以产生振荡信号&#xff0c;而天线&#xff08;通常通过线圈&#xff09;用于发射信号。 LC振荡电路&#xff1a; **线圈&#xff08;L1&#xff09;和电容器&#xff08;C&#xff09;**串…

MySQL 基本语法讲解及示例(上)

第一节&#xff1a;MySQL的基本操作 1. 创建数据库 在 MySQL 中&#xff0c;创建数据库的步骤如下&#xff1a; 命令行操作 打开 MySQL 命令行客户端或连接到 MySQL 服务器。 输入以下命令创建一个数据库&#xff1a; CREATE DATABASE database_name;例如&#xff0c;创建一…

Locust框架

Locust 简介&#xff1a;是一个Python的第三方库&#xff0c;专门用来进行性能并发测试 Locust特点&#xff1a; 基于Python的开源负载测试工具支持多种操作系统支持二次开发能够模拟更多用户基于协程&#xff08;微线程&#xff09;的并发 Jmeter与Locust对比 Locust安装 …

街道网格宣传稿件投稿我知道了好方法

作为街道信息宣传员,我的日常是将街道的每一项重要活动、每一份温暖故事编织成文字,传递给公众。这份工作既充满挑战又极具意义,但在最初,我却在这份看似简单的任务上屡屡受阻。那时,我的投稿方式单一且传统——依赖电子邮件,将稿件发送至各大媒体的投稿箱。我本以为,只要内容足…

Linux操作系统学习:day03

内容来自&#xff1a;Linux介绍 视频推荐&#xff1a;[Linux基础入门教程-linux命令-vim-gcc/g -动态库/静态库 -makefile-gdb调试]( 目录 day0317、创建删除目录创建目录删除目录 18、文件的拷贝19、mv 命令20、查看文件内容的相关命令21、给文件创建软连接或硬链接 day03 …

docker部署dm数据库

官方文档参考 官网地址&#xff1a;https://eco.dameng.com/document/dm/zh-cn/start/dm-install-docker.html 下载镜像地址 docker部署 1、加载镜像 docker load -i dm8_20240613_x86_rh6_64_rq_ent_8.1.3.140_pack5.tar使用docker images&#xff0c;查看镜像和镜像标签…