【C++小白到大牛】红黑树那些事儿

news2024/9/20 16:29:59

目录

前言:

一、红黑树的概念

二、红黑树的性质

三、红黑树结点的定义

四、红黑树的插入

情况一:u存在且为红

情况二:u不存在/u存在且为黑

小总结:

原码:

五、红黑树的检验

六、性能比较


前言:

我们之前已经学过了二叉搜索树的优化版——AVL树,这次我们来学习二叉搜索树的另外一种优化版本——心心念念的红黑树。

之前还是初学者的博主觉得红黑树简直就是神明般的存在,而现如今也能理清楚红黑树的基本框架和插入规则,不禁感慨万分。忆往昔峥嵘岁月,展未来任重道远~

一、红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。因此下面我们就来研究怎样的一个配色规则使得红黑树接近平衡。

二、红黑树的性质

  1.  每个结点不是红色就是黑色 
  2. 根节点是黑色的  
  3. 如果一个节点是红色的,则它的两个孩子结点必须是黑色的,没有连续的红色节点  
  4.  每条路径均包含相同数目的黑色结点  

问题:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

答:因为在上面的规则制约下,最长路径就是一黑一红,而最短路径就是全黑,又因为每条路径的黑色结点数量相同,因此能推出上面的结论最长路径中节点个数不会超过最短路径节点个数的两倍!

三、红黑树结点的定义

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	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)
	{}
};

问题:在节点的定义中,为什么要将节点的默认颜色给成红色的?也就是新增节点为什么是红色?

答:

因为新插入结点颜色是红色不一定错误,但是插入黑色一定有问题

插入黑色结点为什么难以控制,违反规则?因为你在任何一个叶子结点的位置插入黑色结点就不满足,每条路径的黑色结点的数量是一样的,没法解决! 

四、红黑树的插入

第一步: 按照二叉搜索的树规则找新节点的插入位置

这里根二叉搜索树的所有先前插入规则是一样的,都需要根据二叉搜索树的性质去找到新插入结点的具体位置

第二步:检测新节点插入后,红黑树的性质是否造到破坏

因为默认新插入结点的颜色是红色,所以首先需要判断父亲的颜色:

  1. 插入位置的父亲是黑色,不需要处理,插入结束
  2. 插入位置的父亲是红色,出现了连续的红色结点,需要处理

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

所以下面的所有情况都是根据插入结点的父亲是红色来判断。我们来根据叔叔的情况来分情况讨论~

我们为什么要以u的存在与否、颜色与否来分情况讨论呢?因为只有叔叔的结点是不确定的,c、p、g颜色都是确定的!因为c、p颜色都是红色,而g的颜色一定是黑色,因为不能有两个连续的红色结点,所以只有叔叔是未知数。

情况一:u存在且为红

解决方法:p/u变黑,g变红,如果g是根,再把g变黑,如果g不是根,继续往上处理(g当成c,此时的g就相当于新插入的红色结点c,循环判断,直到根节点为止)

爷爷为什么要变红?因为爷爷有可能不是整个树的根,为了保持当前树的黑色结点不变,如果爷爷是根,将爷爷变成黑即可。

这里的a/b/c/d/e不用管,p/u是g的左或者右都不影响,cur是p的左或者右也不影响,处理方式都是一样的

情况二:u不存在/u存在且为黑

下面是u不存在的情况:

这时就已经违反了红黑树的规则,就需要我们进行旋转处理

p为g的左孩子,cur为p的左孩子,则进行有单旋转;相反,p为g的右孩子,cur为p的右孩子,则进行左单旋转

颜色变换:p、g变色,p变黑,g变红

下面是u存在且为黑的情况

这里也是单旋,因此在结构一样的情况下,u不存在和u存在且为空的情况处理方式都是一样的


上面因为因插入的结点都是与原先结点在同一侧,因此直接采用单旋就可以解决问题,下面我们来讲解,新插入结点与原先结点不在同一侧,需要先单旋变为同一侧,接着再以根节点为旋转点,朝着另外一个方向进行旋转,这里就是双旋,与AVL树旋转的思路几乎一样

下面是u不存在的情况,旋转最后将cur和g变色

然后是u存在且为黑的情况

小总结:

p为g的左孩子,cur为p的右孩子,左右双旋+变色

p为g的右孩子,cur为p的左孩子,右左双旋+变色

看是否在一边,如果在一边直接单旋即可,若不在,先单旋变为同一边,接着再单旋即可。

原码:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv); // 红色的
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = 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)
					{
						//       g
						//    p    u
						// c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//       g
						//    p     u
						//      c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				} 
			}
			else
			{
				Node* uncle = grandfather->_left;
				// 情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					// 继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					// 情况二:叔叔不存在或者存在且为黑
					// 旋转+变色
					//      g
					//   u     p
					//            c
					if (cur == parent->_right)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//		g
						//   u     p
						//      c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;

		return true;
	}

五、红黑树的检验

思路:

要想验证这棵树是不是红黑树,需要从红黑树的概念入手,分为以下几点:

  1. 根是黑的
  2. 没有连续的红色结点
  3. 每条路径的黑色结点的数量相同

可以先遍历一条路径直接到根节点,以这条路径的黑色结点作为基准值,然后再接着遍历其他路径,看其他路径的黑色结点数量是否与其相同,进行判断即可。

bool Check(Node* cur, int blackNum, int refBlackNum)
	{
		if (cur == nullptr)
		{
			if (refBlackNum != blackNum)
			{
				cout << "黑色节点的数量不相等" << endl;
				return false;
			}

			//cout << blackNum << endl;
			return true;
		}

		if (cur->_col == RED && cur->_parent->_col == RED)
		{
			cout << cur->_kv.first << "存在连续的红色节点" << endl;
			return false;
		}

		if (cur->_col == BLACK)
			++blackNum;
		
		return Check(cur->_left, blackNum, refBlackNum)
			&& Check(cur->_right, blackNum, refBlackNum);
	}

	bool IsBalance()
	{
		if (_root && _root->_col == RED)
			return false;

		int refBlackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if(cur->_col == BLACK)
				refBlackNum++;

			cur = cur->_left;
		}

		return Check(_root, 0, refBlackNum);
	}

六、性能比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,红黑树降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多

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

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

相关文章

Linux知识复习第4期

web服务器的基本用法 目录 1、安装 2、启动 3、默认发布目录 1、安装 yum install nginx -y # nginx安装 yum install httpd -y # apache安装 2、启动 systemctl enable --now httpd systemctl enable --now nginx 3、默认发布目录 /usr/www/html/ # …

Python OpenCV 影像处理:影像轮廓

► 前言 上篇介绍使用OpenCV Python对于图像上的二值化操作&#xff0c;二值化主要用途包括图像分割、物体侦测、文字识别等。这种转换可以帮助检测图像中的物体或特定特征&#xff0c;并提取有用的信息&#xff0c;本篇基于二值化操作进行近一步的操作&#xff0c;透过影像梯…

一六七、Linux安装go并部署go项目

Linux 下安装 Golang 获取Golang下载地址 标准官网&#xff1a;https://go.dev/国内镜像官网&#xff1a;https://golang.google.cn/ 安装 1. 进入终端&#xff0c;登入root su - root2. 来到应用安装目录 cd /usr/local3. 使用 wget 下载 如果没有安装 wget 可通过软件…

《向量数据库指南》——Dopple LAbs:展望未来:构建多模态交互的尖端体验

Dopple LAbs:展望未来:构建多模态交互的尖端体验 在快速迭代的科技领域,Dopple LAbs正以其前瞻性的视野和创新精神,引领着人机交互的新篇章。Sam及其团队近期通过一系列技术突破,显著增强了其服务的沉浸感和互动性,为用户带来了前所未有的视听盛宴。以下,我们将深入探讨…

智慧农业大数据助力智慧农业建设

1. 智慧农业概述 智慧农业作为现代农业发展的重要方向&#xff0c;融合了互联网、大数据、云计算、物联网等现代信息技术&#xff0c;旨在提高农业生产效率&#xff0c;实现精准化管理和产品溯源。通过智慧农业的实施&#xff0c;可以解决传统农业面临的信息不对称、融资困难等…

打工人上班适合用的蓝牙耳机推荐?几款开放式耳机推荐

日常工作的话&#xff0c;我还是比较推荐开放式蓝牙耳机的&#xff0c;它特别适合那些需要在长时间工作中保持专注和舒适度的环境&#xff0c;那开放式耳机其实还有一些主要的优点&#xff1a; 减少耳朵疲劳&#xff1a;由于开放式耳机不需要紧密贴合耳朵&#xff0c;因此可以…

复习之 java 锁

裁员在家&#xff0c;没有面试机会&#xff0c;整理整理面试知识点吧&#xff01; 不得不知道的java 锁 Java 中&#xff0c;提供了两种方式来实现同步互斥访问&#xff08;也就是锁&#xff09;&#xff1a;synchronized 和 Lock 多线程编程中&#xff0c;有可能会出现多个线…

使用静态住宅代理解锁YouTube营销的新维度

YouTube作为众多跨境商家的重要营销推广阵地&#xff0c;YouTube的运营数据与店铺的开单息息相关。那么如何做好YouTube营销来增加产品的知名度呢&#xff1f;如何高效运营YouTube矩阵并防止账号间的关联呢&#xff1f;下文介绍的静态住宅代理就能在YouTube营销上助你一臂之力。…

HTML知识点二——表单

表单&#xff1a; 基本语法&#xff1a; <form method"post" action"xxx"><p>名字&#xff1a;<input name"name" type"text"></p><p>密码&#xff1a;<input name"pass" type"pass…

音频进阶学习二——模数和数模转换中的采样、量化和编码

文章目录 前言一、频率连续时间信号的频率数字信号的频率 二、模数转换过程A/D转换三步 三、采样确定采样频率数模转换中的插值 四、量化量化过程量化误差 五、编码总结 前言 所有软件的运行都得益于硬件上的突破&#xff0c;数字信号是从40年前就开始高速发展的领域。得益于硬…

【机器学习】深度强化学习–RL的基本概念、经典场景以及算法分类

引言 深度强化学习&#xff08;Deep Reinforcement Learning, DRL&#xff09;是机器学习的一个分支&#xff0c;它结合了深度学习&#xff08;Deep Learning&#xff09;和强化学习&#xff08;Reinforcement Learning, RL&#xff09;的技术 文章目录 引言一、深度强化学习–…

为什么 React 的函数组件每次渲染执行两次

1.这是 React18 才新增的特性。 2.仅在开发模式("development")下&#xff0c;且使用了严格模式("Strict Mode")下会触发。 生产环境("production")模式下和原来一样&#xff0c;仅执行一次。 在 React 中&#xff0c;当你看到某些代码执行了多…

整合Rocketmq实现审批流消息推送

文章目录 Docker 部署 RocketMQ拉取 RocketMQ 镜像创建容器共享网络 部署NameServer创建目录并授予权限拷贝启动脚本启动容器NameServer 部署Broker Proxy创建挂载文件夹并授权创建broker.cnf文件拷贝启动脚本启动容器Broker 部署RocketMQ控制台&#xff08;rocketmq-dashboar…

12、springboot3 vue3开发平台-前端-记住我功能实现

文章目录 1. 前端用户信息保存2. 登录页面添加3. 后端实现 1. 前端用户信息保存 使用pinia持久化保存用户名密码 src/stores/remember-me.js // 定义 store import { defineStore } from "pinia" import {reactive} from vueexport const useRememberMeStore defi…

求职Leetcode算法题(7)

1.搜索旋转排序数组 这道题要求时间复杂度为o&#xff08;log n&#xff09;&#xff0c;那么第一时间想到的就是二分法&#xff0c;二分法有个前提条件是在有序数组下&#xff0c;我们发现在这个数组中存在两部分是有序的&#xff0c;所以我们只需要对前半部分和后半部分分别…

element ——tree组件懒加载数据、自定义label、修改高亮样式、回显点击状态

需求 整体宽高占一屏&#xff0c;超出滚动条tree组件点击懒加载每一级数据&#xff0c;一共三级三级节点前加icon&#xff0c;标识是否已学习点击高亮显示背景图横向超出省略显示或者横向滚动条纵向超出纵向滚动条修改其字体和间距☆☆☆☆☆从别的页面跳入回显三级点击状态 …

netsh int tcp show global查看TCP参数

TCP 全局参数 接收方缩放状态 : enabled 接收窗口自动调节级别 : normal 加载项拥塞控制提供程序 : default ECN 功能 : disabled RFC 1323 时间戳 : allowed 初始 RTO : 1000 接收段合并状态 : enabled 非 Sack Rtt 复原 : disabled 最大 SYN 重新传输次数 : 4 快速打开 : en…

CrowdTransfer:在AIoT社区中实现众包知识迁移

这篇论文的标题是《CrowdTransfer: Enabling Crowd Knowledge Transfer in AIoT Community》&#xff0c;由 Yan Liu, Bin Guo, Nuo Li, Yasan Ding, Zhouyangzi Zhang, 和 Zhiwen Yu 等作者共同撰写&#xff0c;发表在《IEEE Communications Surveys & Tutorials》上。以下…

springboot航班进出港管理系统--论文源码调试讲解

第2章 开发环境与技术 本章节对开发航班进出港管理系统管理系统需要搭建的开发环境&#xff0c;还有航班进出港管理系统管理系统开发中使用的编程技术等进行阐述。 2.1 Java语言 Java语言是当今为止依然在编程语言行业具有生命力的常青树之一。Java语言最原始的诞生&#xff…

网络协议--TCP/IP协议栈--三握和四挥

文章目录 网络设备交换机交换机的工作原理 路由器路由器工作原理 TCP/IP协议栈TCP/IP四层模型TCP/IP通信过程TCP特性TCP包头结构源端口、目标端口序列号(seq)确认号(小ack)标记位 TCP协议端口号端口号分类ssh服务nc工具抓包 socket套接字端口占用 三次握手Wireshark抓包tcpdump…