红黑树详解及代码实现(C++)

news2024/11/25 12:37:17

红黑树定义

红黑树是一种二叉搜索树,但在每个节点上增加一个存储位标识节点的颜色,RED或BLACK。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而接近平衡。(可以看出红黑树的控制平衡的条件没有AVL树那么严格)

红黑树的性质

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

红黑树一些操作要点

定义方面

为了满足根节点是黑色,我们增加了一个头结点并变成黑色,这个头结点的左指向最小结点,右指向最大节点。

插入方面

红黑树是在二叉搜索树上加上了颜色限制,因此红黑树的插入可以分为两步:
1.按照二叉搜索树的方式插入
2.检测新节点插入后,红黑树的性质是否遭到了破坏。

由于新节点的默认颜色是红色,此时我们就可能双亲结点颜色,如果双亲结点是黑色,则满足红黑树性质;但如果双亲结点时红色时,就不满足性质三。

接下来我们来细看这三种情况
(cur是当前节点,p为父节点,g为祖父节点,u为叔叔结点)

情况一:cur为红,p为红,g为黑,u存在且为红

此时又可以分为,该树是一颗完整的树还是一棵子树
当它是一棵完整的子树时,g也就是根节点必须时黑色,不为黑要改成黑色
当它是一棵子树时,g不是根节点,g就必须有双亲结点,此时就有两个连续的红色结点,这是就必须向上调整

在这里插入图片描述
简述一下解决方案就是:将p ,u改为黑,g改成红,然后把g当成cur,继续向上调整

情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑(外侧)

此时我们主要看u这两种情况:
1.如果u不存在,则cur一定是新插入结点,因为如果不是新插入结点,为了满足性质4,每条路径上的黑色节点个数相同,则cur和p一定有一个是黑色结点。
2.如果u存在,则其一定是黑色的,那么cur结点原来的颜色一定是黑色的,现在是红色的原因是,cur的子树在调整的过程中将cur结点的颜色由黑变红

现在看下子树旋转变色的过程
1.p为g的左孩子,cur为p的左孩子,则进行右单旋
2.p为g的右孩子,cur为p的右孩子,则进行左单旋
p,g变色----p变黑,g变红

在这里插入图片描述

情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑(内侧)

1.p为g的左孩子,cur为p的右孩子,则针对p做左单旋
2.p为g的右孩子,cur为p的左孩子,则针对p做右单旋

红黑树的验证

红黑树的验证也分为两步:
1.验证是否满足二叉搜索数(中序遍历是否为有序序列)
2.检测是否满足红黑数的五种性质

代码实现

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

using namespace std;

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)
	{}
};

template<class K, class V>
struct 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 < kv)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv > kv)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_col = RED;

		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;
			assert(grandfather);
			assert(grandfather->_col == BLACK);

			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//uncle存在且为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					//情况二三,不存在或者存在且为黑
					//情况二:右单旋+变色
					//			g
					//		p		u
					//	c
					if (cur == parent->_left)
					{
						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//(parent==grandfather->_right)
			{
				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;

	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool IsBalance()
	{
		if (_root == nullptr)
		{
			return true;
		}

		if (_root->_col == RED)
		{
			cout << "根节点不是黑色" << endl;
			return false;
		}

		//看黑色结点数
		int bechmark = 0;
		return PrevCheck(_root, 0, bechmark);
	}
private:
	bool PrevCheck(Node* root, int blacknum, int& benchmark)
	{
		if (root == nullptr)
		{
			if (benchmark == 0)
			{
				benchmark = blacknum;
				return true;
			}

			if (blacknum != benchmark)
			{
				cout << "某条黑色节点数不对" << endl;
				return false;
			}
			else
			{
				return true;
			}
		}

		if (root->_col == BLACK)
		{
			blacknum++;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "存在连续的红色结点" << endl;
			return false;
		}

		return PrevCheck(root->_left, blacknum, benchmark)
			&& PrevCheck(root->_right, blacknum, benchmark);
	}

	void _InOrder(Node* root)
	{
		if (_root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right); 
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent == ppNode;
		}
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

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

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subL;
			}
			else
			{
				ppNode->_right = subL;
			}
			subL->_parent = ppNode;
		}
	}
private:
	Node* _root = nullptr;
};

红黑树和AVL数的比较

红黑树和AVL数都是高效平衡二叉树,增删查改的时间复杂度都是O(logn),不存在什么,最坏的时间复杂度。
红黑树不追求绝对平衡,只需保证最长路径不超过最短路径的2倍,相较而言降低了插入和旋转的次数,因此增删效率会比AVL数高一点,实际上使用红黑树可能会更多一点。

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

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

相关文章

共同转债,新化转债上市价格预测

共同转债基本信息转债名称&#xff1a;共同转债&#xff0c;评级&#xff1a;A&#xff0c;发行规模&#xff1a;3.8亿元。正股名称&#xff1a;共同药业&#xff0c;今日收盘价&#xff1a;32.66&#xff0c;转股价格&#xff1a;27.14。当前转股价值 转债面值 / 转股价格 * …

TPU编程竞赛系列|基于TPU-MLIR实现UNet模型部署,比“快”更快!

由算能举办的TPU编程竞赛-AI应用挑战赛正式开赛啦&#xff01;本次大赛会为选手们提供一个预训练的分割模型UNet及数据集&#xff0c;无需训练模型&#xff0c;选手使用算能的MLIR开源编译器进行编译、量化及调优&#xff0c;兼顾精度与推理速度&#xff0c;最终实现UNet模型在…

社媒营销14问

&#x1f447;点击一键关注主笔&#xff1a;邹小困、邝晴岚主持人&#xff1a;增长黑盒分析师刘千出品&#xff1a;增长黑盒研究组前言移动互联网和智能终端的发展&#xff0c;共同催生了很多社交媒体&#xff0c;并吸引了大批量的用户。社交媒体已经成为日常生活的一部分&…

RTOS概念及线程的引入

目录 RTOS的概念 用人来类比单片机程序和RTOS 程序简单示例 提出问题 RTOS的概念 用人来类比单片机程序和RTOS 妈妈要一边给小孩喂饭&#xff0c;一边加班跟同事微信交流&#xff0c;怎么办&#xff1f; 对于单线条的人&#xff0c;不能分心、不能同时做事&#xff0c;她只…

深入解读云场景下的网络抖动 | 龙蜥技术

文/eBPF 技术探索 SIG 一、网络抖动背景 延时高&#xff0c;网络卡&#xff0c;卡住了美好&#xff01; 应用抖&#xff0c;业务惊&#xff0c;惊扰了谁的心&#xff1f; 当你在观看世界杯梅西主罚点球突然视频中断了几秒钟 当你在游戏中奋力厮杀突然手机在转圈圈无法响应…

毕业设计-基于大数据的新闻推荐系统-python

目录 前言 课题背景和意义 实现技术思路 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科…

Qt第四十三章:弹出框QDialog

弹出框类型 ①模态框(阻塞窗口)&#xff1a;QDialog().exec() ②窗口模态框(阻塞当前窗口)&#xff1a;QDialog().open() ③非模态框(非阻塞)&#xff1a;QDialog().show() 弹出框事件 ①触发accept()信号返回1 ②触发reject()信号返回0 ③触发done(int)信号返回int ④擦除弹…

MySql 事务的ACID与实现原理

数据库的事务是并发控制的基本单位&#xff0c;是指逻辑上的一组操作&#xff0c;要么全部执行&#xff0c;要么全部不执行。 一、事务的ACID&#xff1a; &#xff08;1&#xff09;原子性&#xff1a;事务是一个不可分割的工作单元&#xff0c;事务里的操作要么都成功&…

华秋干货分享|PCB电气安全间距设计规则

PCB工程师在设计电子产品的过程中&#xff0c;不能只考虑设计出来的精度以及完美要求&#xff0c;还有很大一个制约条件就是生产工艺的能力问题&#xff0c;因此DFM可制造性分析非常重要。避免设计出来的产品无法生产浪费时间及成本的问题发生。 那么走线层的可制造性都有那些…

nodejs092学生考勤请假管理系统vue

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.3 B/S结构 4 2.4 MySQL数据库 4 前端技术&#xff1a;nodejsvueelementui 前端&#xff1a;HTML5,CSS3、JavaScript、VUE 系统…

我阳了

大家好&#xff0c;我是kaiyuan。我阳了。太开心可以如此轻松自由地公开说出这三个字&#xff0c;毕竟在不久之前&#xff0c;这三个字可能会让我的生活走向完全改变&#xff0c;所有人也都是对这3个字讳莫如深。所以虽然在阳的期间身体非常难受&#xff0c;心理倒是十分轻松舒…

6种更优雅书写Python代码!

1 简介 一些比较熟悉pandas的读者朋友应该经常会使用query()、eval()、pipe()、assign()等pandas的常用方法&#xff0c;书写可读性很高的「链式」数据分析处理代码&#xff0c;从而更加丝滑流畅地组织代码逻辑。 但在原生Python中并没有提供类似shell中的管道操作符|、R中的…

MMPs-PEG-BSA 多基质金属蛋白酶-聚乙二醇-牛血清白蛋白

产品名称&#xff1a;多基质金属蛋白酶-聚乙二醇-牛血清白蛋白 英文名称&#xff1a;MMPs-PEG-BSA 质量控制&#xff1a;95% 原料分散系数PDI&#xff1a;≤1.05 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;避湿 用 途&#xff1a;仅供科研实验使用&#xff0c;不用…

RK3399平台开发系列讲解(中断篇)掌握信号处理

🚀返回专栏总目录 文章目录 一、信号的基本概念二、信号处理流程三、可重入与异步信号安全3.1、可重入函数3.2、异步信号安全沉淀、分享、成长,让自己和他人都能有所收获!😄 📢信号在操作系统中有悠久的历史,信号的概念和使用方式都非常简单,但是要编写出真正实用而稳…

Py根据对象的某个属性排序,比大小

目录 核心代码 一个练习题 核心代码 方法1 不对原来的list进行改变 m sorted(需要排序的list集合, keylambda x: x.对象的属性名) 括号中加上reverseTrue表示反转顺序&#xff0c;你默认的顺序是从小到大加上之后变成从大到小OK 方法2 对原来的list进行排序 cmp operator…

javaSE -面向对象编程(包,继承,组合,多态,抽象类,接口)

一、包&#xff08;package&#xff09; 1.1、包&#xff08;package&#xff09;是组织类的一种方式 包里存的基本上都是类&#xff0c;而这些类都是别人写好的。我们只需要拿着用。前提是导入对应的包 比如说&#xff1a;打印数组 import java.util.Arrays; public class T…

鲜花店如何数字化转型,鲜花店管理小程序

鲜花的用途非常广泛&#xff0c;除了平时祝福送亲友外&#xff0c;还有七夕/情人节送情侣/爱人等&#xff0c;商圈中的各品牌花店也都不少&#xff0c;并且其收益也相当可观&#xff0c;虽然是非必需品&#xff0c;但却又不可缺。 雨科网观察了鲜花行业相关数据报告后&#xff…

基于java+springmvc+mybatis+vue+mysql的学生竞赛模拟系统

项目介绍 本系统采用java语言开发&#xff0c;后端采用springboot框架&#xff0c;前端采用vue技术&#xff0c;数据库采用mysql进行数据存储。 前台&#xff1a; 首页、公交信息、论坛交流、试卷、校园资讯、个人中心、后台管理 后台&#xff1a; 首页、个人中心、用户管理…

别再随意说 Redis 的 SET 保障原子性,在客户端不一定

分布式系统有一个特点&#xff0c;就是无论你学习积累多少知识点&#xff0c;只要在分布式的战线中&#xff0c;总能遇到各种超出主观意识的神奇问题。比如前文使用Jedis来实现分布式锁的技术知识点储备&#xff0c;本以为很稳不会再遇到什么问题&#xff0c;但实际情况却是啪啪…

Android ASM

文章目录逆波兰表达式与字节码的关系中缀表达式转换为逆波兰表达式&#xff08;后缀表达式&#xff09;的过程逆波兰表达式求值过程ASM 的使用ASM 常用 api 说明ClassWriter构造函数传参 flags 的作用定义类的属性&#xff1a;visit()定义类的方法&#xff1a;visitMethod()定义…