C++数据结构 —— 红黑树

news2025/1/16 8:14:01

目录

1.红黑树概念

 2.红黑树节点的定义

3.红黑树的插入操作

4.红黑树的调整动作

4.1调整动作1

4.2调整动作2

4.3调整动作3

4.4插入算法的完整代码

4.5验证红黑树

4.6完整代码

1.红黑树概念

与AVL树一样,红黑树也是map、set等关联式容器的底层结构。但红黑树是现代主流的底层结构,STL使用的便是红黑树。其原因在于:AVL树保持平衡的方法太过于绝对(必须保证每个节点的左右子树的高度差不超过1),而红黑树的性质保证了其具有一定的"柔韧性"以及可观的效率。

红黑树的本质也是一颗二叉搜索树,但在原有的基础上做了平衡处理。红黑树的每个节点都会增加一个存储位,用来表示节点的颜色,可以是红色也可以是黑色

 红黑树具有以下几点性质:

        1.每个节点不是红色就是黑色

        2.根节点必须是黑色

        3.如果一个节点是红色的,它的父节点或者两个子节点都必须为黑色

        4.任何一条路径上的黑色节点数量都是相等的

        5.空节点也认为是一颗红黑树,它也是假象的黑色

满足上面的条件之后,就能达到具有一定"柔韧性"的平衡:红黑树的最长路径的节点个数不会超过最短路径节点个数的两倍。其原因如下图:

 2.红黑树节点的定义

enum Corlor	//枚举颜色
{
	RED,BLACK
};

template <class K>	//为了方便,使用K模型
struct RBTreeNode
{
	RBTreeNode<K>* _left;
	RBTreeNode<K>* _right;
	RBTreeNode<K>* _parent;
	K _key;
	Corlor _cor;

	RBTreeNode(const K& key)
		:_left(nullptr),_right(nullptr),_parent(nullptr),
		_key(key),_cor(RED)
	{}
};

3.红黑树的插入操作

红黑树的插入方式与二叉搜索树一样,需要注意的是,插入的新节点的颜色可以红色,也可以是黑色,但是我们选择红色。因为如果新插入的节点是黑色,那么势必会破坏性质4,当红黑树有多条路径时,维护的成本会非常大。如果新插入的节点是红色,可以赌它的父节点是黑色,如果违反了性质3,我们仅需做一些微调即可。

template <class K>
class RBTree
{
	typedef RBTreeNode<K> Node;
public:
	bool insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			_root->_cor = BLACK;	//根节点的颜色必须是黑色
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(key);
		cur->_cor = RED;	//新插入的节点颜色为红色
		if (key < parent->_key)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else if (key > parent->_key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		// 此时如果新插入的节点破坏了红黑树的性质,就必须做一些微调
		while (parent && parent->_cor == RED)
		{
			// 调整动作
		}
	}
private:
	Node* _root = nullptr;
};

4.红黑树的调整动作

红黑树的调整分为三种动作, 需要调整的情况分别为:

        1.cur为红色,parent为红色,gradfather(parent的父节点)为红色,并且uncle(parent的兄弟节点)节点存在且为红色

        2.cur为红色,parent为红色,grandfather为黑色(这三个节点同在一侧),uncle存在且为黑色或uncle不存在

        3.cur为红色,parent为红色,grandfather为黑色(这三个节点不在同一侧),uncle存在且为黑色或uncle不存在

4.1调整动作1

针对情况1, 可以做出如下调整动作:

        1.让parent和uncle的颜色变黑

        2.让grandfather的颜色变红

        3.如果grandfather为根节点,则让其颜色变黑,结束调整;如果grandfather为某一子树,那么让cur = grandfather,parent = cur->_parent继续向上调整

4.2调整动作2

对于情况2,它是由情况1变过来的:

以这种情况为例,有AVL树的基础可以很明显的看出要以grandfather为轴进行一个右单旋;那么对应的,如果grandfather、parent、cur在右侧连成一线,就要使用左单旋。旋转完成之后,需要将parent置黑色,grandfather置红色

4.3调整动作3

对于情况3,它是由情况1变过来的:

 有过AVL树基础,可以明显的看出,此时要以parent为轴做一个左单旋;再以grandfather为轴做一个右单旋。事实上,当以parent为轴做一个左单旋时候,就立马回到了情况2。旋转完成之后,需要将cur置黑色,grandfather置红色

 综上,情况1可以转化为情况2或情况3,如果没有转化,证明调整结束;针对情况2,调整完毕即可结束;针对情况3,调整完毕即可结束。

4.4插入算法的完整代码

在调整红黑树时会使用旋转算法,AVL树中已经介绍过了。大家仅需把AVL树中的旋转算法的有关平衡因子的部分删除掉即可。

bool insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			_root->_cor = BLACK;	//根节点的颜色必须是黑色
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(key);
		cur->_cor = RED;	//新插入的节点颜色为红色
		if (key < parent->_key)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else if (key > parent->_key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		// 此时如果新插入的节点破坏了红黑树的性质,就必须做一些微调
		while (parent && parent->_cor == RED)
		{
			// 调整动作
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_cor == RED)
				{
					// 情况1

					parent->_cor = uncle->_cor = BLACK;
					grandfather->_cor = RED;
					
					cur = grandfather;
					parent = cur->_parent;
				}
				else	// unle不存在或uncle存在且为黑
				{
					if (parent->_left == cur)
					{
						// 情况2
						RotateR(grandfather);
						parent->_cor = BLACK;
						grandfather->_cor = RED;
					}
					else if(parent->_right == cur)
					{
						// 情况3
						RotateL(parent);
						RotateR(grandfather);
						cur->_cor = BLACK;
						grandfather->_cor = RED;
					}

					break;	//关键
				}
			}
			else if (grandfather->_right == parent)	//镜像即可
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_cor == RED)
				{
					parent->_cor = uncle->_cor = BLACK;
					grandfather->_cor = RED;

					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						// 情况2
						RotateL(grandfather);
						parent->_cor = BLACK;
						grandfather->_cor = RED;
					}
					else if (parent->_left == cur)
					{
						// 情况3
						RotateR(parent);
						RotateL(grandfather);
						cur->_cor = BLACK;
						grandfather->_cor = RED;
					}
					break;	//关键
				}
			}
		}

		_root->_cor = BLACK;	//确保根节点颜色为黑色
		return true;
	}

4.5验证红黑树

逐一验证红黑树的各个性质即可。

bool isRBTree()
	{
		if (_root == nullptr)
		{
			return true;	//空树也可以是红黑树
		}

		if (_root->_cor != BLACK)
		{
			cout << "根节点不为黑色!" << endl;
			return false;	//根节点颜色不为黑色
		}

		// 计算任意一条路径的黑色节点数量
		int black_cnt = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_cor == BLACK)
			{
				++black_cnt;
			}
			cur = cur->_left;
		}

		return __isRBTree(_root, 0, black_cnt);
	}

	bool __isRBTree(const Node* root, int cnt, int key)
	{
		if (root == nullptr)
		{
			if (cnt != key)
			{
				cout << "每条路径的黑色节点数量不相等!" << endl;
				return false;
			}
			return true;
		}

		if (root->_cor == BLACK)
		{
			++cnt;
		}
		
		// 顺便判断是否存在连续的红节点
		Node* parent = root->_parent;
		if (parent && parent->_cor == RED && root->_cor == RED)
		{
			cout << "存在连续的红节点!" << endl;
			return false;
		}

		return __isRBTree(root->_left, cnt, key) && __isRBTree(root->_right, cnt, key);
	}

4.6完整代码

红黑树

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

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

相关文章

大数据开发学习好找工作么

工作到底好不好找&#xff0c;市场需求是一方面&#xff0c;更多的还是要看个人成长背景和实际能力抛开两点都不谈就单说好找或者不好找纯属有点耍流氓了大数据需求越来越多&#xff0c;只有技术在手不愁找不到工作。 大数据开发主要是负责大数据挖掘&#xff0c;大数据清洗处…

谷歌验证码的使用

1. 表单重复提交之验证码 1.1 表单重复提交三种常见情况 提交完表单。服务器使用请求转来进行页面跳转。这个时候&#xff0c;用户按下功能键 F5&#xff0c;就会发起最后一次的请求。造成表单重复提交问题。解决方法&#xff1a;使用重定向来进行跳转用户正常提交服务器&…

行为型模式之策略模式

行为型模式&#xff1a;类和对象如何交互&#xff0c;划分责任和算法&#xff0c;即对象之间通信。 概念 策略模式是对算法的包装&#xff0c;是把使用算法的责任和算法本身分割开来&#xff0c;委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面…

Servlet请求响应

文章目录Servlet请求响应进阶内容回顾Servlet 体系结构get/post 请求Servlet 的生命周期web.xml 配置文件HttpServletRequest 接口文件上传HttpServletResponse 接口文件下载响应表格的案例转发与重定向简介请求转发原理请求转发案例重定向原理重定义与请求转发的区别Servlet请…

分巧克力(二分)

儿童节那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。小明一共有 N 块巧克力&#xff0c;其中第 i 块是 HiWi 的方格组成的长方形。为了公平起见&#xff0c;小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足&#xff1a;形…

百度“松果“ OJ赛第一周 题解

百度"松果" OJ赛第一周 题解 第一周的周赛基本考察的都是模拟和递推递归问题&#xff0c;虽然不涉及很难的算法&#xff0c;但是还是比较考察代码能力和思维能力的。 1.数据流的中位数 题意&#xff1a;要求你做一个系统可以进行两个操作&#xff0c;第一个操作是…

cloud flare 真不错(常规思路)

2022-10-20 前言 接到一个测试目标&#xff0c;开局cloudflare&#xff0c;最后运气不错还是拿下了。因授权测试等原因&#xff0c;文章仅展示思路历程。 过程 信息搜集 给的目标是test.com&#xff0c;前期经过一些基本的信息搜集&#xff0c;发现了一个求职子域employee…

FreeRTOS入门

目录 一、简介 二、堆的概念 三、栈的概念 四、从官方源码中精简出第一个FreeRTOS程序 五、修改官方源码增加串口打印 一、简介 FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统&#xff0c;功能包括&#xff1a;任务管理、时间管理、信号量、消息队列、…

嵌入式开发:McObject eXtremeDB嵌入式数据库系统

嵌入式数据库已经成为数据库技术的一种流行应用&#xff0c;尤其是对于企业中的物联网应用。有很多理由将数据库嵌入到应用程序的端点中&#xff0c;而不仅仅是将数据推送到设备中。嵌入式开发人员在选择嵌入式数据库时&#xff0c;真正重要且与众不同的解决方案是写入速度、大…

【边缘端环境配置】英伟达Jetson系列安装pytorch/tensorflow/ml/tensorrt环境(docker一键拉取)

【边缘端环境配置】英伟达Jetson系列安装pytorch/tensorflow/ml/tensorrt环境&#xff08;docker一键拉取&#xff09;0.JetPack1.安装输入法2.安装docker和nvidia-docker3.拉取l4t-pytorch镜像4.拉取l4t-tensorflow镜像5.拉取l4t-ml镜像6.拉取tensorrt镜像7.镜像换源8.其他&am…

三数之和(双指针 or hash表)

给你一个整数数组nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]]满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 3 < …

ubuntu22.04 Desktop 服务器安装

操作系统 使用的是Uubntu22.04 Desktop的版本&#xff0c;系统安装后&#xff0c;默认开启了53端口和631端口 关闭udp 5353、53791端口&#xff08;avahi-daemon服务&#xff09; sudo systemctl stop avahi-daemon.socket avahi-daemon.service sudo systemctl disable ava…

[1.2]计算机系统概述——操作系统的发展与分类

文章目录第一章 计算机系统概述操作系统的发展与分类&#xff08;一&#xff09;手工操作阶段&#xff08;二&#xff09;批处理阶段——单道批处理系统&#xff08;三&#xff09;批处理阶段——多道批处理系统&#xff08;四&#xff09;分时操作系统&#xff08;五&#xff…

【Java开发】JUC进阶 01:Lock锁详解

1 Lock锁介绍已经在【JUC基础】04简单介绍过了&#xff0c;本文做进一步的拓展&#xff0c;比如公平锁和非公平锁、&#x1f4cc; 明白锁的核心四个对象&#xff1a;线程&#xff0c;共享资源&#xff0c;锁&#xff0c;锁操作包括线程如何操作资源&#xff0c;使用锁锁哪个资源…

xgboost: 分割查找算法:贪婪算法、分桶算法

1、Basic Exact Greedy Algorithm 树学习的关键问题之一是找到最好的分割&#xff0c;如Eq(7)所示。 贪婪算法:分割查找算法枚举所有特征上的所有可能的分割。精确的贪婪算法如Alg. 1所示。为了高效地完成这一任务&#xff0c;算法必须首先根据特征值对数据进行排序&#xff…

SpringMVC 参数绑定(视图传参到控制器)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Vue组件基础(父向子、子向父、子向子传值)

Vue组件基础-父向子、子向父、子向子传值一、Vue组件概念,创建和使用1.1 组件概念1.2 组件基础使用1.3 组件-scoped作用二、Vue组件通信2.1 父向子传值(props)2.2 子向父传值($emit)2.3 子与子传值(EventBus)一、Vue组件概念,创建和使用 1.1 组件概念 组件是可复用的Vue实例,封…

【100个 Unity实用技能】 | 脚本无需挂载到游戏对象上也可执行的方法

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…

springboot使用ssh公钥连接mysql(含账号密码连接)

引言 在项目开发过程中&#xff0c;遇到了连接数据库时需要使用ssh公钥的情况。在本地使用navicat可以直接通过可视化界面去进行ssh的连接&#xff0c;但是在java中无法直接去进行连接。 后来经过查询资料&#xff0c;发现必须要在java中编写相关配置文件后才可以正常连接。 …

Linux内核源码进程原理分析

Linux内核源码进程原理分析一、Linux 内核架构图二、进程基础知识三、Linux 进程四要素四、task_struct 数据结构主要成员五、创建新进程分析六、剖析进程状态迁移七、写时复制技术一、Linux 内核架构图 二、进程基础知识 Linux 内核把进程称为任务(task)&#xff0c;进程的虚…