红黑树内容及代码实现

news2024/11/17 17:40:26

目录

1.概念

2.性质

3.实现

3.1定义数据类型

3.2设计基本操作

3.2.1着色问题详解

3.2.2 代码基本框架

3.2.3着色问题代码

3.2.4红黑树的销毁

3.3验证基本操作

4.总结

1.概念

红黑树是一种二叉搜索树,但是在其中的每个结点上增加一个存储表示该节点的颜色,这份颜色便是一种标记,可以是Red也可以是Black。通过对任何一条从根到叶子结点的路径,加以颜色的限制,来保证最长路径中节点数量不超过最短路径中节点数量的2倍。

因此,从红黑树的定义和规则出发,红黑树表现出一种近似平衡的二叉搜索树,但是其性能方面完全不亚于AVL树。

2.性质

  • 每个结点不是黑色便是红色;
  • 根节点是黑色的;
  • 如果一个节点是红色,则它的两个孩子是黑色(不存在连在一起的红色节点);
  • 对于每个结点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数量的黑色节点;
  • 每个叶子节点(空节点)都是黑色的(保证空树的根节点为黑色)。

通过上述性质的限制,我们便可以保证最长路径中节点个数不超过最短路径节点个数的2两倍。具体原因我们可以借助画图来理解,如下图:

我们可以进行结合红黑树性质进行假设,来让一段路径存在的节点数尽可能多,一段路径尽可能少。于是我们考虑极端情况,即红黑树中全部节点都为黑色。

此时,我们便可与插入尽可能多的红色节点。于是我们根据上图可以发现,在这种极端情况下,才存在某一简单路径上的节点个数是另一简单路径的2倍。当这种红黑树节点全为黑色的情况是不存在的,所以也不会存在最长路径中节点个数超过或等于最短路径节点个数2倍的情况。

3.实现

实现红黑树时,我们引入头节点head的内容,使它的左指针域指向begin(),右指针域指向end(),它的parent设置为根节点root,root的parent设置为头节点head。这样安排便于我们快速定位首尾元素位置,并且对于我们后续自己实现map和set存在很大帮助。

(创建RBTree.hpp文件)

3.1定义数据类型

我们来定义红黑树中的数据类型,并给出它的构造方法,最终代码设计如下:

enum Color { RED, BLACK }; //枚举类型

template<class T>
struct RBTreeNode {
	//搜索树具有的左右节点
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	//加入双亲节点便于后续向上更新
	RBTreeNode<T>* _parent;
	T _data;//数据类型
	Color _color;//颜色类型

	//默认为节点赋红色,更好的保护性质中:每个简单路径中黑色节点数量相同
	RBTreeNode(const T& data = T(), Color color = RED)
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _color(color)
	{}
};

3.2设计基本操作

然后我们来给出红黑树中的基本框架和节点插入。我们需要注意的内容是,红黑树较于AVL树,其最大的不同之处在于它的节点具有颜色,也就是我们对其节点存在标记。并且这些颜色(标记)是存在规则(红黑树性质)的。    

所以当我们对红黑树进行基本操作时,对于其中节点的颜色考虑是至关重要的,我们必须保证我们的操作不仅能到达我们想要的预期内容,还需保证我们的操作不会破坏红黑树应有的规则。

对于上述内容提到的红黑树节点着色处理问题,我们在此展开进行讨论。

3.2.1着色问题详解

对于红黑树中的新节点插入,我们需要对新节点插入后,红黑树的性质是否遭到破坏进行检测。若性质满足则直接退出,否则我们对红黑树进行旋转着色处理。

具体而言便是:我们对于新插入的节点默认是为红色,因此:若新插入节点的双亲是黑色,则没有违反红黑树规则;但当新插入节点的双亲为红色时,便违背了红黑树规则中:不能存在连续的红色节点。此时,便需要我们对于红黑树节点的着色情况进行处理。

情况一:当新插入节点cur默认为红色,父节点p为红色,和父节点同根的字节点u为红色,父节点的父节点(祖父节点)g为黑色,如下图:

在这种情况中,存在两种可能,即g为根节点和g为子树。

当g为根节点时,我们可以按照上面方式进行调整,使p节点和u节点变为黑色,g节点变为红色,并最后将根节点g调整为黑色(红黑树性质:根节点必须为黑色)。

当g为字数时,我们将p、u节点调整为黑色,g调整为红色。然后将p当作新的cur来继续线上进行调整,直到该红黑树满足着色规则。

情况二:cur为红色(p的左孩子),p为红色,g为黑色,u不存在/u存在且为黑色。

此时我们将u的不存在情况和u为黑色情况一并考虑,原因如下;

  • 若u不存在,则cur一定为新插入节点,因为cur不是新插入节点的话,那么p和cur一定是存在一个颜色为黑色,否则会出现2红节点相邻,不满足红黑树规则;
  • 若u为黑色,那么cur原本颜色一定为黑色(此时cur为红色,可能是cur为新插入节点默认为红色,也可能是红黑树经历某次调整使cur为红色),否则不满足红黑树中任意路径黑色节点数量相同规则。

对于前者我们仅需将cur制为黑色即可,对于后者,我们需要进行以下操作:当p为g的左孩子,且cur为p的左孩子时,则进行右单旋转;当p为g的右孩子,且cur为p的左孩子时,则进行左单旋转。

情况三:cur为红色(p的右孩子),p为红色,g为黑色,u不存在/u存在且为黑色,如下图:

当p为g的左孩子,且cur为p的右孩子时,针对p进行左单旋转;相反,当p为g的右孩子,且cur为p的左孩子时,针对p进行右单旋转。则但四种情况便会转化为情况二。继续情况二步骤即可。

3.2.2 代码基本框架

我们设计代码框架如下:

//假设红黑树中的data唯一
template<class T>
class RBTree {
	typedef RBTreeNode<T> Node;
public:
	RBTree() {
		Node* _head = new Node();//值域设置空,并且不设置head颜色(head不算在RBTree中)
		//此时不存在节点,head的parent指向null,左右指针域指向自身
		_head->_parent = nullptr;
		_head->_left = _head;
		_head->_right = _head;
	}

	~RBTree() {
		Destory(_head->_parent);
	}
	//插入节点
	bool Insert(const T& data);
	//旋转
	void RotateLeft(Node* parent);
	void RotateRight(Node* parent);
	//销毁
	void Destory(Node*& root);
	//中序遍历
	void InOrder(Node* root);

	Node* GetHead() {
		return _head;
	}
private:
	//获取红黑树中最小节点,即最左则节点
	Node* LeftMost() {
		Node* root = _head->_parent;
		if (nullptr == root) {
			return _head;
		}
		//寻找最左侧节点
		Node* cur = root;
		while (cur) {
			cur = cur->_left;
		}
		return cur;
	}
	//获取红黑树中最大节点,即最右则节点
	Node* RightMost() {
		Node* root = _head->_parent;
		if (nullptr == root) {
			return _head;
		}
		//寻找最右侧节点
		Node* cur = root;
		while (cur) {
			cur = cur->_right;
		}
		return cur;
	}
protected:
	Node* _head;
};

3.2.3着色问题代码

在理解3.2.1中的着色问题之后,我们便可设计具体的节点插入代码(分情况讨论)如下;

template<class T>bool RBTree<T>::Insert(const T& data) {
	Node*& root = _head->parent;

	//空树,直接插入空节点
	if (root == nullptr) {
		root = new Node(data, BLACK);
		root->_parent = _head;
	}
	//非空
	else {
		Node* cur = root;
		Node* parent = _head;

		//向下遍历,根据AVL树模式来寻求插入节点位置
		while (cur) {
			//记录插入节点的父节点
			parent = cur;
			if (data < cur->_data) {
				cur = cur->_left;
			}
			else if (data > cur->_data) {
				cur = cur->_right;
			}
			else {
				return false;
			}
		}

		//在寻求的合适位置插入新节点
		cur = new Node(data);//新节点默认颜色为红色
		if (data < parent->_data) {
			parent->_left = cur;
		}
		else if (data > parent->_data) {
			parent->_right = cur;
		}
		//添加插入节点的父节点
		cur->_parent = parent;

		//双亲不为头节点,且双亲不为根节点(根节点在红黑树中默认为黑色) 
		while (parent != _head && RED == parent->_color) {
			Node* grandFather = parent->_parent;

			//分情况讨论,判断parent是grandFather的左孩子还是右孩子
			//此时为我们上述讨论的三种情况
			if (parent == grandFather->_left) {
				Node* unclue = grandFather->_right;//此时u节点为祖父节点的右节点
				//情况一:u节点存在,且为红色
				if (unclue && RED == unclue->_color) {
					unclue->_color = BLACK;
					parent->_color = BLACK;
					grandFather->_color = RED;
					//继续向上更新
					cur = grandFather;
					parent = cur->_parent;
				}
				//情况二(三):u节点不存在或为黑色
				else {
					//先处理情况三,使其改变为情况二
					if (cur == parent->_left) {
						//自旋和交换指针指向
						RotateLeft(parent);
						swap(parent, cur);
					}
					//此时为情况二,变色和自旋
					parent->_color = BLACK;
					grandFather->_color = RED;
					RotateRight(grandFather);
				}
			}
			//此时为我们上述讨论三种情况的反向情况(需要自旋处理)
			else {
				Node* unclue = grandFather->_left;//此时u节点为祖父节点的左节点
				if (unclue && RED == unclue->_color) {
					//情况一反向
					parent->_color = BLACK;
					unclue->_color = BLACK;
					grandFather->_color = RED;
					cur = grandFather;
					parent = cur->_parent;
				}
				//情况二(三)反向
				else {
					//处理情况三反向
					if (cur == parent->_left) {
						RotateRight(parent);
						swap(cur, parent);
					}
					//此时为情况二反向
					parent->_color = BLACK;
					grandFather->_color = RED;
					RotateLeft(grandFather);
				}
			}
		}
	}

	//更新头节点的左右指针域
	_head->_left = LeftMost();
	_head->_right = RightMost();

	//规则二:根节点为黑色
	root->_color = BLACK;
	return true;
}

//左单旋
template<class T>void RBTree<T>::RotateLeft(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL) {
		subRL->_parent = parent;
	}

	subRL->_left = parent;
	Node* pparent = parent->_parent;
	parent->_parent = subR;
	subR->_parent = pparent;

	if (pparent == _head) {
		_head->_parent = subR;
	}
	else {
		if (parent == pparent->_left) {
			pparent->_left = subR;
		}
		else {
			pparent->_right = subR;
		}
	}
}

//右单旋
template<class T>void RBTree<T>::RotateRight(Node* parent) {
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR) {
		subLR->_parent = parent;
	}

	subL->_right = parent;
	Node* pparent = parent->_parent;
	subL->_parent = pparent;
	parent->_parent = subL;

	if (pparent == _head) {
		_head->_parent = subL;
	}
	else {
		if (parent == pparent->_left) {
			pparent->_left = subL;
		}
		else {
			pparent->_right = subL;
		}
	}
}

3.2.4红黑树的销毁

template<class T>void RBTree<T>::Destory(Node*& root) {
	if (root) {
		Destory(root->_left);
		Destory(root->_right);
		delete root;
		root = nullptr;
	}
}

3.3验证基本操作

我们给出中序遍历来查看红黑树结构和主函数中运行方法,如下:

//中序遍历
template<class T>void RBTree<T>::InOrder(Node* root){
	cout << "中序遍历结果:";
	if (root) {
		InOrder(root->_left);
		cout << root->_data << " ";
		InOrder(root->_right);
	}
	cout << endl;
}


template<class T>
int main() {
	RBTree<int> t;
	int arr[] = { 16,3,7,11,9,26,18,14,15 };
	for (auto i : arr) {
		t.Insert(i);
	}
	auto temp = RBTree<T>::GetHead();
	t.InOrder(temp->_parent);
	return 0;
}

4.总结

红黑树是一种自平衡的二叉搜索树,能够保证对于n个节点的红黑树,树高不超过2log(n+1)。其中每个节点包含两个重要属性,即颜色和存储数据。

在我们对红黑树中的节点进行操作的时候,我们需要对其中每个节点的操作加以考虑,即注意在我们的操作过程中,保证红黑树的规则不被打破,这是使用红黑树的关键--保证其原有性质不变。

红黑树是一种广泛使用的数据结构,它的高效性、自平衡性和可靠性使得其成为一种广泛应用的数据结构。

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

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

相关文章

【数据结构】栈和队列(队列篇)

上期我们已经学习了数据结构中的栈&#xff0c;这期我们开始学习队列。 目录 1.队列的概念及结构 2.队列的实现 队列结构体定义 常用接口函数 初始化队列 队尾入队列 队头出队列 获取队列头部元素、 获取队列队尾元素 获取队列中有效元素个数 检测队列是否为空 销毁…

chatgpt赋能python:用Python轻松给手机用户发送短信——优秀的工具在手,无限可能!

用Python轻松给手机用户发送短信——优秀的工具在手&#xff0c;无限可能&#xff01; 作为一个有10年Python编程经验的工程师&#xff0c;我想分享一下如何用Python给手机用户发送短信。Python是目前非常流行的编程语言之一&#xff0c;它可以轻松地完成很多任务。而给用户发…

13.定时器中断

1.通用定时器工作过程&#xff1a; 2.时钟选择&#xff1a; 内部时钟(CK_INT);外部时钟模式1&#xff1a;外部输入脚(TIx)&#xff1b;外部时钟模式2&#xff1a;外部触发输入(ETR)&#xff1b;内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器&#xff0c;例如可…

【Kafka面试题1】Kafka消费者是pull(拉)还是push(推)模式,这种模式有什么好处?

Kafka消费者是pull(拉)还是push(推)模式&#xff0c;这种模式有什么好处&#xff1f; 一、概述回答 Kafka中的Producer和consumer采用的是push-and-pull模式&#xff0c;即Producer只管向broker push消息&#xff0c;consumer只管从broker pull消息&#xff0c;两者对消息的生…

从零用自己数据跑R3LIVE

1、相机内参标定 相机选用4mm的广角相机&#xff0c;相机内参标定选择用最常见的棋盘格方法&#xff0c;首先安装ROS自带的包 sudo apt install ros-melodic-camera-calibration 用usb_cam启动相机后进行标定 。 rosrun camera_calibration cameracalibrator.py --size 8x6…

【Linux】网络编程相关概念介绍、UDP套接字简单演示、最简单的UDP公共聊天室实现~

文章目录 [toc] 网络编程 - 套接字一些概念1. 源ip地址与目的ip地址2. 端口号 和 socket套接字 **问题 3. 源端口号和目的端口号4. 认识TCP协议基本特点5. 认识UDP协议基本特点6. 网络字节序 socket编程接口**struct sockaddr**接口演示: 简单的UDP网络通信int socket()UDP网络…

读书笔记-《ON JAVA 中文版》-摘要18[第十八章 字符串-1]

文章目录 第十八章 字符串1. 字符串的不可变2. 的重载与 StringBuilder3. 意外递归4. 字符串操作5. 格式化输出5.1 printf()5.2 System.out.format()5.3 Formatter 类5.3.1 格式化修饰符5.3.2 Formatter 转换 5.4 String.format() 6. 自我学习总结 第十八章 字符串 字符串操作…

【专题速递】更多的解决方案:传统行业不再「传统」

// 音视频技术作为企业数字化转型的关键技术与能力之一&#xff0c;为众多传统行业在生产、服务、管理与维护等方面提供了强有力的支持。那么&#xff0c;音视频技术是如何助力企业数字化转型的&#xff1f;7月28日LiveVideoStackCon上海站数字化与行业案例专场&#xff0c;为…

【AI】PyTorch安装记录及Anaconda环境配置

【AI】PyTorch安装记录及Anaconda环境配置 说下本地环境&#xff0c;RTX4070 12GB GPU&#xff1b;618刚买&#xff0c;不能让他闲着&#xff0c;配置一下炼丹环境&#xff0c;开始为打工人工作。为了方便后续部署模型之间依赖不冲突&#xff0c;所以使用Anaconda管理Python环…

【数据结构】第 1~10 章:思维导图与重点汇总

目录 一、概论 &#xff08;1&#xff09;思维导图 &#xff08;2&#xff09;常见名词 &#xff08;3&#xff09;数据结构的定义 &#xff08;4&#xff09;抽象数据类型 ADT &#xff08;5&#xff09;算法 &#xff08;6&#xff09;评价算法的好坏的因素 &am…

Python基础 —— 循环语句

如约来更新循环语句了.说到循环&#xff0c;有一定编程基础的小伙伴们都知道&#xff0c;我们最常用的循环莫过于 while循环&#xff0c;for循环和goto循环&#xff08;不过goto也不怎么常用&#xff09;&#xff0c;所以今天就来说一说 while循环和 for循环 来看一下本文大致…

IMX6ULL系统移植篇-uboot基础命令

一. uboot 启动 当设备上电启动时&#xff0c;需要马上按下回车键&#xff0c;开发板启动会停止在 uboot的启动Log信息时刻。 这就是 uboot的命令模式&#xff0c;即可以输入 uboot命令进行一些操作。 二. uboot 基础命令 1. help 命令 当开发板上电启动后&#xff0c;马…

电脑如何设置外网内网一起使用

如果你的电脑支持连接无线网&#xff0c;就可以设置内网外网一起使用。一般情况下&#xff0c;连接无线网还是网线都是系统自动链接的,但有时候开发中需要内网外网一块使用&#xff0c;不用手动切换网络。 首先确保我们的电脑有双网卡&#xff0c;可以两个都是有线网卡&#xf…

【机械臂视觉抓取从理论到实战】

1. 概述 GR-CNN&#xff1a;https://paperswithcode.com/paper/antipodal-robotic-grasping-using-generative 2. 环境搭建及模型训练 GR-CNN&#xff1a;https://github.com/skumra/robotic-grasping 下载源码创建环境 #下载robotic-grasping源码 git clone https://github.…

CVE-2021-3493:Overlay 文件系统 Ubuntu 本地提权漏洞分析

分析此漏洞的文章非常多&#xff0c;在此只是记录一下复现漏洞的过程以及对漏洞的个人理解。Linux 内核漏洞有一定的准入门槛&#xff0c;不适合小白阅读。 基本信息 [影响范围] Ubuntu 14.04 ~20.10 [漏洞描述] Ubuntu 内核代码允许低权限用户在使用 unshare() 函数创建的…

李彦宏:AI原生应用比大模型数量更重要

6月26日&#xff0c;百度创始人、董事长兼首席执行官李彦宏出席“世界互联网大会数字文明尼山对话”&#xff0c;发表了题为 《大模型重塑数字世界》 的演讲。 大模型是当下全球科技创新的焦点&#xff0c;也是全球人工智能竞赛的主战场。李彦宏认为&#xff0c;“新的国际竞争…

箱线图概念和使用介绍

箱线图时一种针对连续型变量的统计图。通常用作比较。 箱子中间的一条线&#xff0c;是数据的中位数&#xff0c;代表了数据的平均水平。 箱子的上限和下限&#xff0c;分别是数据的上四分位数和下四分位数&#xff0c;意味着箱子包含50%的数据。因此&#xff0c;箱子的高度在…

23.RocketMQ之NameServer处理Broker心跳包,更新本地路由信息

NameServer处理Broker心跳包,更新本地路由信息 DefaultRequestProcessor继承自NettyRequestProcessor:处理各种客户端的请求&#xff0c;如果请求类型是为REGISTER_BROKER&#xff0c;则将请求转发到RouteInfoManager#regiesterBroker,主要是服务器端 或者客户端或者broker发送…

go语言环境安装

文章目录 环境介绍安装软件包步骤环境变量设置来一个经典的hello worldNice 最近的项目需要用到go来开发了&#xff0c;前几天就已经在看书了&#xff0c;今天是个周末&#xff0c;先在家里的机器上把环境搭好&#xff0c;特此记录一下。 环境介绍 下载地址&#xff1a;https:…

RRT 算法研究(附 Python / C++ 实现)

RRT 算法研究 参考 机器人路径规划、轨迹优化课程-第五讲-RRT算法原理和代码讲解 机器人路径规划之RRT算法(附C源码) RRT算法(快速拓展随机树)的Python实现 《基于改进RRT算法的路径规划研究》 《面向室内复杂场景的移动机器人快速路径规划算法研究》 理论基础 RRT&#xff0…