【数据结构 08】红黑树

news2024/11/28 2:33:27

一、概述

红黑树,是一种二叉搜索树,每一个节点上有一个存储位表示节点的颜色,可以是Red或Black。

通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长上两倍,因而是接进平衡的。

红黑树性质:

  • 根节点是黑色
  • 红节点的两个孩子一定是黑色的;黑节点的两个孩子不一定是红色的。没有连续的红节点
  • 对于每个节点,从该节点到其后所有后代叶节点的简单路径上,均包含相同数目的黑节点
  • 每个叶子节点都是黑色的(NIL空节点)

二、算法

红黑树在设计的时候,插入策略与AVL树一样,只是插入之后的调整策略与AVL不同(旋转策略是一样的,但是红黑树需要考虑变色且无需再考虑平衡因子)

只看遍历的时间复杂度的话,AVL树的时间复杂度是低于红黑树的,因为AVL树的时间复杂度是无限接近于O(\log_2 n),而红黑树的时间复杂度是O(\log_2 n) ~2 * O(\log_2 n),但这在系统层面的时间损失很小。

从调整策略的角度,红黑树的调整次数与旋转次数都远低于AVL树。所以综合来看,红黑树的性能是优于AVL树的,map和set的底层封装的也正是红黑树。

三、调整策略

红黑树的根节点一定是黑色,新插入的节点默认为是红色。

当新插入一个红色节点cur时,先观察cur的父节点parent,如果父节点是黑色,则无需调整;如果父节点也是红节点,那么再观察cur节点的叔叔节点uncle,根据uncle节点的情况进行调整。

红黑树调整策略的核心思路:不能出现连续的红色节点,每条路径的黑色节点数量一样

调整策略分为三种情况:

  • 情况1:父节点parent和叔叔节点uncle都是红色,此时只需变色调整,不需旋转,并向上调整
  • 情况2:父节点parent为红色,叔叔节点不存在或为黑色,cur节点和parent节点都同为左节点或同为右节点,此时需要左单旋或者右单旋,无需向上调整
  • 情况3:父节点parent为红色,叔叔节点不存在或为黑色,cur节点为左节点时parent节点为右节点,或者cur节点为右节点时parent节点为左节点,此时需要左右双旋或者右左双旋,无需向上调整

情况1:parent节点是红色,uncle节点也是红色

调整方法:parent节点与uncle节点变为黑色,祖父节点grandparent节点变为红色,然后将cur变为祖父节点,parent节点依然为cur节点的父节点,向上调整,直到出现parent节点为空,最后再将根节点置为黑色。

如图:

情况1

 

情况1

情况2: 父节点parent为红色,叔叔节点不存在或为黑色,cur节点和parent节点都同为左节点或同为右节点

调整方法:以祖父节点grandparent为轴点进行左单旋或则右单旋,父节点变成黑色,祖父节点变成红色。

如图:

情况2
情况1 情况2 结合调整

情况3:父节点parent为红色,叔叔节点不存在或为黑色,cur节点为左节点时parent节点为右节点,或者cur节点为右节点时parent节点为左节点

调整方法:先以parent节点为轴心进行左单旋或者右单旋,再以grandparent节点为轴心进行与上一步操作相反的单旋,最后将cur节点变成黑色,将grandparent节点变为红色

示例:将数列{ 16, 3, 7, 9, 26, 18, 14, 15, 13, 11 }按顺序插入红黑色中

图1 插入16、3、7
图2 插入9
图3 插入26、18
图4 插入14、15
图5 插入13
图6 插入11

四、RBTree.h

#define _CRT_SECURE_NO_WARNINGS 1

#pragma once
#include <iostream>

enum Color
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	std::pair<K, V> kv;
	RBTreeNode* parent;
	RBTreeNode* left;
	RBTreeNode* right;
	Color col;

	RBTreeNode(const std::pair<K, V>& x)
		: kv(x), parent(nullptr), left(nullptr), right(nullptr), col(RED)
	{}
};

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

		// 寻找新节点该插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->kv.first > x.first)
				cur = cur->left;
			else if (cur->kv.first < x.first)
				cur = cur->right;
			else
				return false;
		}

		// 创建新节点
		cur = new Node(x);
		cur->parent = parent;
		if (parent->kv.first > x.first)
			parent->left = cur;
		else
			parent->right = cur;

		// 调整颜色
		while (parent && parent->col == RED)
		{
			Node* grandpa = parent->parent;
			if (grandpa->left == parent)
			{
				Node* uncle = grandpa->right;
				if (uncle && uncle->col == RED)
				{
					// 情况1,变色
					parent->col = uncle->col = BLACK;
					grandpa->col = RED;

					cur = grandpa;
					parent = cur->parent;
				}
				else
				{
					if (parent->left == cur)
					{
						// 情况2,右单旋
						_RotateRight(grandpa);

						parent->col = BLACK;
						grandpa->col = RED;
					}
					else
					{
						// 情况3,左右双旋
						_RotateLeft(parent);
						_RotateRight(grandpa);

						cur->col = BLACK;
						grandpa->col = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = grandpa->left;
				if (uncle && uncle->col == RED)
				{
					// 情况1,变色
					parent->col = uncle->col = BLACK;
					grandpa->col = RED;

					cur = grandpa;
					parent = cur->parent;
				}
				else
				{
					if (parent->right == cur)
					{
						// 情况2,左单旋
						_RotateLeft(grandpa);

						parent->col = BLACK;
						grandpa->col = RED;
					}
					else
					{
						// 情况3,右左双旋
						_RotateRight(parent);
						_RotateLeft(grandpa);

						cur->col = BLACK;
						grandpa->col = RED;
					}
					break;
				}
			}
		}

		_root->col = BLACK;
		return true;
	}

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

	bool IsBalance()
	{
		if (_root == nullptr)
			return true;
		if (_root->col == RED)
			return false;

		// 计算最左路径上的黑节点数量
		int ref = 0;
		Node* left = _root;
		while (left)
		{
			if (left->col == BLACK)
				++ref;
			left = left->left;
		}

		return _IsBalance(_root, 0, ref);
	}

private:
	void _RotateLeft(Node* parent)
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;

		parent->right = subRL;
		if (subRL)
			subRL->parent = parent;

		subR->left = parent;
		Node* ppNode = parent->parent;
		parent->parent = subR;
		if (ppNode == nullptr)
		{
			_root = subR;
			subR->parent = nullptr;
		}
		else
		{
			if (ppNode->left == parent)
				ppNode->left = subR;
			else
				ppNode->right = subR;
			subR->parent = ppNode;
		}
	}

	void _RotateRight(Node* parent)
	{
		Node* subL = parent->left;
		Node* subLR = subL->right;

		parent->left = subLR;
		if (subLR)
			subLR->parent = parent;

		subL->right = parent;
		Node* ppNode = parent->parent;
		parent->parent = subL;
		if (ppNode == nullptr)
		{
			_root = subL;
			subL->parent = nullptr;
		}
		else
		{
			if (ppNode->left == parent)
				ppNode->left = subL;
			else
				ppNode->right = subL;
			subL->parent = ppNode;
		}
	}

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

		_InOrder(root->left);
		std::cout << "<" << root->kv.first << "," << root->kv.second << "> ";
		_InOrder(root->right);
	}

	bool _IsBalance(Node* root, int blackNum, int ref)
	{
		if (root == nullptr)
		{
			if (blackNum != ref)
			{
				std::cout << "路径黑色节点数量不相等" << std::endl;
				return false;
			}
			return true;
		}
		
		if (root->col == RED && root->parent->col == RED)
		{
			std::cout << "路径出现连续红节点" << "<" << root->kv.first << "," << root->kv.second << "> " << std::endl;
			return false;
		}

		if (root->col == BLACK)
			++blackNum;

		return _IsBalance(root->left, blackNum, ref)
			&& _IsBalance(root->right, blackNum, ref);

	}

private:
	Node* _root = nullptr;
};

五、test.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "RBTree.h"
#include <ctime>

void test1_RBTree()
{
	int arr[] = { 16, 3, 7, 9, 26, 18, 14, 15, 13, 11 };
	//int arr[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	RBTree<int, int> t;
	for (auto& e : arr)
	{
		t.Insert(std::make_pair(e, e));
	}
	t.InOrder();
	std::cout << std::endl;
	std::cout << t.IsBalance() << std::endl;
}

void test2_RBTree()
{
	RBTree<int, int> t;
	for (int i = 0; i < 100000; ++i)
	{
		int x = rand() % 10000;
		t.Insert(std::make_pair(x, x));
	}
	t.InOrder();
	std::cout << std::endl;
	std::cout << t.IsBalance() << std::endl;
}

int main()
{
	srand(time(nullptr));
	test1_RBTree();
	//test2_RBTree();

	return 0;
}

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

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

相关文章

《区块链简易速速上手小册》第8章:区块链的技术挑战(2024 最新版)

文章目录 8.1 可扩展性问题8.1.1 基础知识8.1.2 主要案例&#xff1a;比特币的可扩展性挑战8.1.3 拓展案例 1&#xff1a;以太坊的可扩展性改进8.1.4 拓展案例 2&#xff1a;侧链和分层解决方案 8.2 安全性与隐私8.2.1 基础知识8.2.2 主要案例&#xff1a;比特币交易的安全性8.…

没有外网Nginx如何配置如何开启https

判断是否支持open-ssl 在服务器执行如下命令 openssl version没有则安装open-ssl&#xff0c;由于服务器没有外网&#xff0c;可以离线安装openssl-3.0.1.tar.gz&#xff0c;我是在有网的服务器直接下载的&#xff0c;然后再上传到这台无网的服务器上 wget https://www.open…

45 漏洞发现-API接口服务之漏洞探针类型利用修复

目录 端口服务类安全测试API接口-webservice RESTful APT 演示案例:端口服务类-Tomcat弱口令安全问题端口服务类-Glassfish任意文件读取其他补充类-基于端口WEB站点又测试其他补充类-基于域名WEB站点又测试其他补充类-基于IP配合端口信息再收集口令安全脚本工具简要使用-Snetcr…

小白级教程,10秒开服《幻兽帕鲁》

在帕鲁的世界&#xff0c;你可以选择与神奇的生物「帕鲁」一同享受悠闲的生活&#xff0c;也可以投身于与偷猎者进行生死搏斗的冒险。帕鲁可以进行战斗、繁殖、协助你做农活&#xff0c;也可以为你在工厂工作。你也可以将它们进行售卖&#xff0c;或肢解后食用。 前言 马上过年…

全网最简单的幻兽帕鲁服务器搭建教程

幻兽帕鲁是一款备受欢迎的多人在线游戏&#xff0c;为了提供更好的游戏体验&#xff0c;许多玩家选择自行搭建服务器。本文将指导大家如何简单快速地搭建幻兽帕鲁服务器&#xff0c;轻松享受游戏的乐趣。 第一步&#xff1a;购买游戏联机服务器 购买入口&#xff1a;https://tx…

充电桩项目实战:短信功能 分布式限流

你好&#xff0c;我是田哥 最近&#xff0c;我在对充电桩项目进行微服务升级中&#xff0c;肯定会遇到一些问题 前面分享了&#xff1a;充电桩项目实战&#xff1a;搞定多数据源&#xff01; 题外话&#xff1a;如果想年后找到更好的工作&#xff0c;推荐看这篇文章&#xff1a…

了解Ansible自动化运维工具及模块的使用

一、Ansible的相关知识 1.1 Ansible工具的了解 Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。Ansible…

如何使用内网穿透工具在公网实现实时监测DashDot服务器仪表盘

文章目录 1. 本地环境检查1.1 安装docker1.2 下载Dashdot镜像 2. 部署DashDot应用3. 本地访问DashDot服务4. 安装cpolar内网穿透5. 固定DashDot公网地址 本篇文章我们将使用Docker在本地部署DashDot服务器仪表盘&#xff0c;并且结合cpolar内网穿透工具可以实现公网实时监测服务…

如何通过CVE漏洞编码找到对应的CVE漏洞详情及源码修改地址

背景&#xff1a; 最近正在使用docker进行一些cve漏洞的复现&#xff0c;有时候就要通过CVE的漏洞编码&#xff0c;找到对应的漏洞详情&#xff0c;以及漏洞的源码修改 以我上一篇文章的CVE-2020-17518编码为例 Apache Flink文件上Apache Flink文件上 方法&#xff1a; 通…

【C++4】内存管理

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

初识C语言·动态内存开辟

目录 1 为什么要有动态内存开辟 2 malloc函数的使用 3 free函数的使用 4 calloc函数的使用 5 realloc函数的使用 6 常见的动态内存开辟的错误 1&#xff09;对空指针的解引用 2&#xff09;对动态内存开辟空间的越界访问 我们使用了calloc函数开辟了10个整型空间&…

【MyBatis】MyBatis是什么?作用?怎么实现?

一、MyBatis是什么 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff08;Plain …

spring-boot-admin的介绍和使用

概述 Spring Boot 有一个非常好用的监控和管理的源软件&#xff0c;这个软件就是 Spring Boot Admin。该软件能够将 Actuator 中的信息进行界面化的展示&#xff0c;也可以监控所有 Spring Boot 应用的健康状况&#xff0c;提供实时警报功能。 主要的功能点有&#xff1a; 显…

排序(4)——快速排序

五、快速排序 1.简介 快速排序是Hoare于1962年提出的&#xff0c;主要采取了分治的思想。快速排序首先确定一个基准值&#xff0c;然后以这个选出的基准值为标准&#xff0c;将整个数组进行按大小进行区分&#xff0c;使得小于该基准值的位于其一侧&#xff0c;大于基准值的位…

超越GPT4 Turbo?科大讯飞发布星火认知大模型3.5版本

简介 1月30日&#xff0c;科大讯飞举行星火认知大模型V3.5升级发布会。科大讯飞董事长刘庆峰、研究院院长刘聪正式发布基于首个全国产算力训练的讯飞星火V3.5&#xff0c;七大核心能力全面提升。 功能展示多模交互 多模理解&#xff1a;上传图片素材&#xff0c;大模型完成识…

C++/数据结构:二叉搜索树的实现与应用

目录 一、二叉搜索树简介 二、二叉搜索树的结构与实现 2.1二叉树的查找与插入 2.2二叉树的删除 2.3二叉搜索树的实现 2.3.1非递归实现 2.3.2递归实现 三、二叉搜索树的k模型和kv模型 一、二叉搜索树简介 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0…

vue——实现多行粘贴到table事件——技能提升

最近在写后台管理系统时&#xff0c;遇到一个需求&#xff0c;就是要从excel表格中复制多行内容&#xff0c;然后粘贴到后台系统中的table表格中。 如下图所示&#xff1a;一次性复制三行内容&#xff0c;光标放在红框中的第一个框中&#xff0c;然后按ctrlv粘贴事件&#xff0…

路由备份聚合排错

目录 实验拓扑图 实验要求 实验排错 故障一 故障现象 故障分析 故障解决 故障二 故障现象 故障分析 故障解决 故障三 故障现象 故障分析 故障解决 故障四 故障现象 故障分析 故障解决 故障五 故障现象 故障分析 故障解决 实验拓扑图 实验要求 按照图示配…

Typora导出html文件图片自动转换成base64

Typora导出html文件图片自动转换成base64 一、出现问题二、解决方案三、编码实现3.1.创建Java项目3.2.代码3.3.打包成Jar包 四、如何使用endl 一、出现问题 typora 导出 html 的时候必须带有原图片&#xff0c;不方便交流学习&#xff0c;文件太多显得冗余&#xff0c;只有将图…

Docker中安装MySql的遇到的问题

目录 一、mysql查询中文乱码问题 1. 进入mysql中进行查看数据库字符集 2. 修改 my.cnf 中的配置 3. 重启mysql容器&#xff0c;使得容器重新加载配置文件 4. 测试结果 二、主从同步中遇到的问题 2.1 Slave_IO_Running:Connecting 的解决方案 1. 确定宿主机防火墙开放my…