数据结构(13)——平衡二叉树(红黑树)

news2024/9/24 20:42:31

欢迎来到博主的专栏——数据结构
博主ID:代码小号

文章目录

    • 红黑树
      • 红黑树节点之间的关系
      • 红黑树的插入
        • uncle节点为红色
        • uncle节点是黑色或者没有uncle节点

红黑树

平衡二叉树最出名的除了AVL树之外就是红黑树(RBTree),所谓红黑树,即拥有以下特性的平衡二叉树

(1)每个节点要么是红色,要么是黑色
(2)根节点必须是黑色
(3)红色节点的子节点必须是黑色
(4)每条路径上的黑色节点个数相同。

红黑树中所谓的路径,并非是从根节点到叶节点经过的所有节点,而是从根节点到NULL节点之间的路径。

以下图为例,该红黑树具有的路径有:
在这里插入图片描述

ok,从上图我们也可以看出,红黑树节点至少有5个对象,分别是指向左右子树的指针,指向父节点的指针,值,以及颜色。那么红黑树节点的定义应该如下:

enum colour
{
	RED,BLACK
};
template<class key,class value>
struct RBtreeNode
{
	pair<key, value> _kv;//值
	RBtreeNode<key, value>* _right;//右子树
	RBtreeNode<key, value>* _left;//左子树
	RBtreeNode<key, value>* _parent;//父节点
	colour _col;//颜色
};

为什么说红黑树也是平衡二叉树?它又是如何做到平衡的呢?

假设一颗红黑树路径的黑色节点为4个,那么可能最短路径为:
在这里插入图片描述
最长路径为:
在这里插入图片描述

由此可得,在一颗路径黑色节点个数为N的红黑树中。最短路径*2>=最长路径。假设在最短路径上进行插入,时间复杂度为O(logN),在最短路径上进行插入的时间复杂度为O(log2N),那么也就说明红黑树即使是在最差的情况下,也能保持插入和查找的对数级的时间复杂度,效率还是很不错的。

由于红黑树的插入与删除与搜索二叉树逻辑相同,只是多了调整平衡二叉树的操作,因此博主不多赘述插入,删除的相关操作,重点讲解如何调整红黑树。

平衡二叉树的旋转操作,博主已在AVL树文章中讲解,甚至用的是与AVL树一样的旋转代码,因此也不多赘述。

红黑树节点之间的关系

假设在红黑树的某部分子树的形状如下:
在这里插入图片描述
假如当前节点cur为红,那么parent只能是黑,grandfather和uncle的颜色不确定。
在这里插入图片描述
如果parent为红,那么grandfather只能是黑色,记住这个结论,很重要。
在这里插入图片描述

假如grandfather是黑色,parent和uncle是红色,将grandfather转换成红色,parent和uncle都变成黑色,该路径下的黑色节点的个数不变。
在这里插入图片描述
这里说明了一个性质,那就是红黑树之间的节点的颜色其实是可以按照一定规律改变的。

红黑树的插入

uncle节点为红色

这里不讨论如何插入,只讲解如何保持红黑树的规则。

首要要考虑一个问题,新插入的节点是红色好还是黑色好?结论是插入的节点是红色比较好,因为我们可以假设插入节点为红色。那么会遇到两种情况
(1)插入在黑色节点之后,符合红黑树的特性
(2)插入在红色节点之后,不符合红黑树不允许红色节点连续的特性

如果插入节点为黑色,那么插入的路径的黑色节点一定会比其他路径多(因为红黑树要求每个路径的黑色节点相同,插入黑色节点会打破这个平衡)。两害相较取其轻,所以插入的节点视为红色,如果不符合特性再进行修改。

RBtreeNode(const pair<key,value>& kv)
	:_kv(kv)
	,_right(nullptr)
	,_left(nullptr)
	,_parent(nullptr)
	,_col(RED)
{}

当我们插入红色节点时,可能会出现以下情况。
(1)插入在黑色节点之后:
红色节点插入在黑色节点之后是符合红黑树规则的,而且每个路径的黑色节点也没有改变,因此种情况是合法的,不用处理
(2)插入在红色节点之后
红黑树的特性是没有连续的红色节点,因此出现这种情况就需要对红黑树进行处理了。

假设插入的节点为cur,其父节点parent是红色节点,那么parent的父节点一定是黑色的,因为红色的节点不能相连,那么我们姑且将其称为祖父(grandfather)节点吧。

在这里插入图片描述

grandfather的右子树存不存在未知,我们先假设祖父节点存在右子树,那么祖父节点一定会只存在一个红色节点。称该节点为叔叔(uncle)节点。为什么叔叔节点一定会是红色呢?因为parent是红色的是确定的条件,因为如果parent是黑色的节点,插入这个红色的cur节点不会构成非法操作,也就不需要处理了,因此引发处理红黑树的条件一定是插入节点的父节点一定是红色的。

那么在这种情况下,uncle节点一定会是红色的,因为如果uncle节点是黑色的,那么祖父节点的左右子树的路径上的黑色节点数量就不一致了。这显然违反了红黑树的规定。
在这里插入图片描述
那么祖父的右边一定只有一个节点呢?因为uncle是红色的,那么uncle的子节点一定是黑色的,那么还是遇到了那个问题,祖父节点的左右子树的路径上黑色节点个数不相同,违反了红黑树的规则。因此如果cur插入在红色节点之后,且祖父节点存在右树时,有且只有这么一种请况:
在这里插入图片描述
那么该怎么处理呢?首先这个情况非法的原因就是parent和cur是连续的红色节点,那么将其改变成黑白相间即可。但无论是parent或者cur变成黑色节点,都会破坏红黑树在路径上的黑色个数。那么我们就不能单独修改某个节点,而是要一起改。其实方法在上面博主已经给出了,博主这里直接截图吧。

在这里插入图片描述
我们让parent和uncle改成黑色,grandfather变成红色,就能解决问题。
在这里插入图片描述
代码如下:

我们先写出检查红黑树的代码:

void keep_balance(Node* parent,Node* cur)
{
	while (parent != nullptr && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (grandfather->_left == parent)//父节点在左
		{
		}
		else//父节点在右
		{
		}
	}
	_root->_col = BLACK;
}

现在grandfather的节点变成红色节点了。那么grandfather的父节点是可能出现红色节点,或者黑色节点的,如果grandfather的父节点是黑色节点,那就说明不需要调整了,已经符合了红黑树的操作。如果grandfather的父节点也是红色节点,那么由于出现了红色节点,那么我们需要对grandfather和grandfather的父节点进行调整。
在这里插入图片描述

以此类推,直到根节点。如果根节点变红,由于红黑树规定根节点不能是红色,因此需要将根节点变黑。
在这里插入图片描述
将根节点变黑相当于给红黑树的所有路径都加了一个黑节点,因此将根节点变黑这个操作在任何情况下都是合法的。所以我们可以直接在程序的最后让根节点变成黑色。

if (grandfather->_left == parent)//uncle节点在grandfather的左子节点
{
	Node* uncle = grandfather->_right;
	if(uncle!=nullptr&&uncle->_col==RED)
	{
		parent->_col = BLACK;
		uncle->_col = BLACK;
		grandfather->_col = RED;
		cur = grandfather;
		parent = cur->_parent;
	}
}
uncle节点是黑色或者没有uncle节点

前面提到的条件是uncle节点是红色的情况,但是uncle节点可能会出现没有或者是黑色这两种情况。

当我们插入cur节点时,如果和parent同样都是红色,就要进行调整,此时uncle节点一定是没有,或者是红色的,不可能存在uncle节点是黑色的情况。
在这里插入图片描述
这是因为,如果uncle节点是黑色,就不符合每个路径的黑色节点个数相同的特性。
在这里插入图片描述
因此只能是uncle节点不存在。如果出现这种情况,我们就要使用旋转的方式将子树进行旋转了。因为很明显子树已经不平衡了,左子树有两个节点,而右子树没有节点,因此需要旋转。旋转也分为左单旋,右单旋,右左单旋和左右单旋,这方面的操作与AVL树的旋转无异,因此博主不再赘述。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果是在向上更新节点的时候,就会遇到uncle是黑色的情况。比如:
在这里插入图片描述

此时的cur节点是由于红黑树向上更新节点颜色时变成红色的,此时也需要旋转。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

整体的平衡代码如下:

void keep_balance(Node* parent,Node* cur)
{
	while (parent != nullptr && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		if (grandfather->_left == parent)//父节点在左
		{
			Node* uncle = grandfather->_right;
			if(uncle!=nullptr&&uncle->_col==RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;
				parent = cur->_parent;
			}
			else 
			{
				if (parent->_left == cur)
				{
					rotateR(grandfather);//右单旋
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					rotateLR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;
			}
		}
		else//父节点在右
		{
			Node* uncle = grandfather->_left;
			if (uncle != nullptr && uncle->_col == RED)
			{
				uncle->_col = BLACK;
				parent->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				if (parent->_right == cur)
				{
					rotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				}
				else if (parent->_left == cur)
				{
					rotateRL(grandfather);
					grandfather->_col = RED;
					cur->_col = BLACK;
				}
				break;
			}
		}
	}
	_root->_col = BLACK;
}

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

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

相关文章

JSON 格式详解

JSON 格式详解 随着互联网的发展和各种 Web 应用程序的普及&#xff0c;数据交换已经成为了我们日常开发中的重要环节。而在各种数据交换格式中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&#xff0c;以其简洁、易于阅…

2024.9.4(k8s)

一、前期准备 1、配置主机映射 [rootk8s-master ~]# vim /etc/hosts 192.168.8.168 k8s-master 192.168.8.176 k8s-node1 192.168.8.177 k8s-node2[rootk8s-master ~]# ping k8s-master 2、配置yum源 [rootk8s-master yum.repos.d]# vim kubernetes.repo [kubernetes] n…

智能医学(二)——MDPI特刊推荐

特刊征稿 01 特刊名称&#xff1a; eHealth and mHealth: Challenges and Prospects, 2nd Volume 参与期刊&#xff1a; 截止时间&#xff1a; 摘要提交截止日期 关闭(2024年6月30日) 投稿截止日期 2024年9月30日 目标及范围&#xff1a; 关键字 l 人工智能 l 计算机…

模拟实现string类及体验传统深拷贝

目录 strcpy 构造函数 优化 拷贝构造/深拷贝 operator size/operator[] operator<< c_str() 模拟string::iterator 插入 push_back() append() operator reserve npos strcpy strcpy是将/0拷贝完成后才会停止。 构造函数 string():_str(nullptr) {} st…

vite 打包 学习

plugins.jsimport vue from "vitejs/plugin-vue"; // 自动引入插件 import autoImport from "unplugin-auto-import/vite"; import setupExtend from "unplugin-vue-setup-extend-plus/vite"; import { ElementPlusResolver } from unplugin-vue…

国内Etsy开店注册账号需要什么?

Etsy作为海外知名二手电商平台&#xff0c;对于原创手工产品的商家来说具有巨大的市场流量与商机&#xff0c;但注册Etsy账号对于国内跨境电商用户来说确实存在一定的难度&#xff0c;作为Etsy也是小有名气的小商家&#xff0c;今天也分享一下开店的经验帮助大家出海。 一、Ets…

终端安全一体化解决方案有哪些?值得收藏的五款终端安全系统

随着信息技术的迅猛发展&#xff0c;企业和个人面临着越来越多的安全威胁。终端作为连接互联网和用户的第一线&#xff0c;其安全性直接影响到整个网络乃至组织的安全态势。为了应对日益复杂的网络环境&#xff0c;许多企业开始采用终端安全一体化解决方案&#xff0c;以期达到…

EVPN学习

三、VXLAN BGP EVPN基本原理_vxlan的type2,type3区别-CSDN博客 华为数通笔记VXLAN&BGP EVPN_vxlan为什么用bgp协议-CSDN博客

【MeterSphere】vnc连接不上selenium-chrome容器

目录 一、现象 二、查看配置文件 docker-compose-seleniarm.yml 三、处理 3.1 删除上图当中的三行 3.2 msctl reload 3.3 重新连接 前言:使用vnc连不上ms的selenium-chrome容器,看不到里面运行情况,以前其实可以,后来不行了,研究了一下 一、现象 输入IP:端口,连…

vmware 17.6 pro for personal USE初体验

新学期开学了&#xff0c;暑假期间把台式机放在办公室远程&#xff0c;无赖期间经常断电&#xff0c;把我的老台给烧坏了&#xff0c;检测了下固态硬盘和机械硬盘&#xff0c;好歹能用。但是win11的系统奔溃了。就花了半天时间重装。*v* 悲剧的是&#xff0c;一些软件环境必须…

怎么合并pdf文件?6个PDF文件合并成一个,只需要这5步!

在日常工作和学习中&#xff0c;我们经常需要处理多个PDF文件&#xff0c;有时为了方便查阅和管理&#xff0c;需要将它们合并成一个文件。以下是几种实用的方法来合并PDF文件&#xff0c;特别是如何将6个PDF文件合并成一个。 PDF合并工具推荐1. 金舟PDF编辑器 第一步、从金舟…

php民宿短租平台Java民宿预约系统python民宿预订住宿与可视化分析系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

【C++ 第十八章】C++11 新增语法(4)

前情回顾&#xff1a; 【C11 新增语法&#xff08;1&#xff09;&#xff1a;1~6 点】 C11出现与历史、花括号统一初始化、initializer_list初始化列表、 auto、decltype、nullptr、STL的一些新变化 【C11 新增语法&#xff08;2&#xff09;&#xff1a;7~8 点】 右值引用和…

爬虫练习(js逆向解密)

目标 网站地址交易列表 - 福建省公共资源交易电子公共服务平台 (fj.gov.cn) 抓取内容如下&#xff1a; 分析 查找js代码 点击下一页翻页的时候&#xff0c;查看请求返回的数据&#xff0c;发现data数据是经过加密后得到的 通过全局搜索data,发现有两千多个结果&#xff0c;一个…

Qt将数据库中的数据导出为html

一、源码分享 bool ReportFormUtils::exportReportHtml(QString &errString, const QString tableName, const QString savePathAndName, const QString tableTitle, const QString tableInfo) {Q_UNUSED(errString)Q_UNUSED(tableName)Q_UNUSED(savePathAndName)#define …

原子放大1亿倍能看到另一个宇宙?微观与宏观是一体的?

原子的行星模型 开始阐述前,先从物质 组成与体积 方面进行一些铺垫与解释:我们身处于物质的世界,大部分物质由分子构成,分子由原子构成,原子由电子、质子、中子构成,质子,中子又是由夸克构成。鉴于人类目前的科技,或许未来也可以知道电子的组成是否有更加微观的存在…

kubernetes中的资源管理

目录 1 资源管理介绍 2 资源管理的方式 2.1 kubectl命令介绍及格式 2.2 资源类型 2.3 kubectl 常见操作指令 2.3.1 CREATE 示例&#xff1a; 2.3.1.1 指定资源类型创建 2.3.1.2 查看创建的资源类型 2.3.1.3 查看pods是否正确被创建并且被调度 2.3.1.4 查看名为shuyan 的 Dep…

Flutter基本组件Text使用

Text是一个文本显示控件&#xff0c;用于在应用程序界面中显示单行或多行文本内容。 Text简单Demo import package:flutter/material.dart;class MyTextDemo extends StatelessWidget {const MyTextDemo({super.key});overrideWidget build(BuildContext context) {return Sca…

我写了个ffmpeg-spring-boot-starter 使得Java能剪辑视频!!

最近工作中在使用FFmpeg&#xff0c;加上之前写过较多的SpringBoot的Starter&#xff0c;所以干脆再写一个FFmpeg的Starter出来给大家使用。 首先我们来了解一下FFmpeg能干什么&#xff0c;FFmpeg 是一个强大的命令行工具和库集合&#xff0c;用于处理多媒体数据。它可以用来做…

【redis】redis编译和redis.conf配置

下载源码 reids 解压编译 # 解压 tar -zxvf redis-5.0.14.tar.gz cd redis-5.0.14/ make PREFIX/opt/redis install# requirepass root # 开启远程访问 bind 0.0.0.0 protected-mode no # 修改日志打印路径&#xff0c;修改redis.conf daemonize yes logfile /var/log/redis.…