【C++】二叉搜索树+变身 = 红黑树

news2024/12/24 14:33:12
头像
🚀个人主页:@小羊
🚀所属专栏:C++
很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~

动图描述

目录

  • 前言
    • 一、定义与性质
    • 二、红黑树节点的定义
    • 三、新增节点插入
    • 四、验证红黑树
    • 五、AVL树和红黑树比较


前言

本文仅适合了解二叉搜索树,但不了解红黑树底层原理的同学阅读哦。

本篇文章不会带你从头到尾实现红黑树,但会带你深入理解红黑树是怎么实现平衡的,怎么通过旋转变换实现保持平衡,以及实现平衡过程中的细节应该怎么处理等。


一、定义与性质

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

#

  1. 根节点是黑色的
  2. 每个节点不是红色就是黑色
  3. 如果一个节点是红色的,则它的两个孩子一定是黑色的(没有连续的红色节点)
  4. 对每个节点,从该节点到其所有后代叶子结点的简单路径上,黑色节点数目相同
  5. 每个叶子节点都是黑色的(此处的叶子结点指的是空节点,通常是为了算路径)

其中第三点和第四点是控制红黑树平衡的关键,与AVL树不同的是,红黑树不再关注高度,只关注颜色,只要控制住了颜色就控制住了高度。


二、红黑树节点的定义

维持二叉搜索树的平衡,旋转处理是必要的,因此红黑树的节点也需要指向其父节点。

enum Colour
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Colour _col;

	RBTreeNode(const pair<K, V>& kv, colour col = RED)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(col)
	{}
};

上面我们将节点的默认颜色设置为红色,为什么不是黑色呢?

新增节点默认设置为红色或黑色都可能会破坏红黑树的性质,默认为红色对红黑树的性质影响最小,就算修改也更为容易。另外红黑树主要还是以黑色节点为主,在红黑树中黑色节点通常都是通过变色而来的,而红色节点往往都是新增的。


三、新增节点插入

虽然新增节点默认为红色,但根节点必须是黑色。

bool Insert(const pair<K, V>& kv)
{
	Node* pcur = _root;
	Node* parent = nullptr;
	if (pcur == nullptr)//当前还没有节点
	{
		_root = new Node(kv);
		_root->_col = BLACK;//根节点必须是黑色的
		return true;
	}
	//...
}

插入一个红色节点,其父亲是红色时才需要处理, 当父亲是红色时其爷爷一定是黑色,不然在插入新节点之前红黑树就已经有问题了。

所以处理过程的关键是看叔叔,大体可以分为两种情况:

其中g表示grandfatherp表示parentu表示unclea,b,c,d,e为红黑子树。

| 情况一:g为黑,p为红,u存在且为红
| 处理方法:变色,继续向上调整

在这里插入图片描述

上图中pcur可能为新增节点,也可能之前为黑,是经过变色而来的。

//当父节点存在且为红时需要调整
while (parent && parent->_col == RED)
{
	if (parent == grandfather->_left)
	{
		uncle = grandfather->_right;
	}
	else
	{
		uncle = grandfather->_left;
	}
	if (uncle && uncle->_col == RED)//情况一:u存在且为红
	{
		parent->_col = BLACK;
		uncle->_col = BLACK;
		grandfather->_col = RED;
		if (grandfather->_parent == nullptr)
		{
			grandfather->_col = BLACK;
			return true;
		}
		pcur = grandfather;
		parent = pcur->_parent;
		grandfather = parent->_parent;
	}
	//...
}

| 情况二:g为黑,p为红,u存在且为黑 / u不存在
| 处理方法:旋转 + 变色

单旋:
在这里插入图片描述

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

	parent->_left = subLR;
	subL->_right = parent;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	Node* parentparent = parent->_parent;
	parent->_parent = subL;
	if (parentparent == nullptr)
	{
		_root = subL;
	}
	else
	{
		if (parent == parentparent->_left)
		{
			parentparent->_left = subL;
		}
		else
		{
			parentparent->_right = subL;
		}
	}
	subL->_parent = parentparent;
}

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

	parent->_right = subRL;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	subR->_left = parent;
	Node* parentparent = parent->_parent;
	parent->_parent = subR;
	if (parentparent == nullptr)
	{
		_root = subR;
	}
	else
	{
		if (parent == parentparent->_left)
		{
			parentparent->_left = subR;
		}
		else
		{
			parentparent->_right = subR;
		}
	}
	subR->_parent = parentparent;
}

双旋:
在这里插入图片描述

//当父节点存在,且颜色为红,需要往上更新
while (parent && parent->_col == RED)
{
	if (parent == grandfather->_left)
	{
		uncle = grandfather->_right;
	}
	else
	{
		uncle = grandfather->_left;
	}
	
	//情况一:u存在且为红
	
	else //情况二:u存在且为黑 / 情况三:u不存在
	{
		if (parent == grandfather->_left 
		&& pcur == parent->_left)
		{
			RotateR(grandfather);
			parent->_col = BLACK;
			grandfather->_col = RED;
		}
		else if (parent == grandfather->_left 
		&& pcur == parent->_right)
		{
			RotateL(parent);
			RotateR(grandfather);
			pcur->_col = BLACK;
			grandfather->_col = RED;
		}
		else if (parent == grandfather->_right 
		&& pcur == parent->_left)
		{
			RotateR(parent);
			RotateL(grandfather);
			pcur->_col = BLACK;
			grandfather->_col = RED;
		}
		else if (parent == grandfather->_right 
		&& pcur == parent->_right)
		{
			RotateL(grandfather);
			parent->_col = BLACK;
			grandfather->_cocl = RED;
		}
		break;
	}
}

总结:

  • 红黑树的插入主要以黑色节点为主,时刻维持黑色节点的数量
  • 处理过程应尽量避免旋转,能变色就不旋转
  • 黑色节点通常都是受限于红黑树的性质而不得不变色而来的,红色节点通常都是新增的

不管是变色还是旋转,始终都要保持插入新节点前后各路径上黑色节点的数量。


四、验证红黑树

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
  2. 检测其是否满足红黑树的性质

检测红黑树的性质,主要检测红黑树的根节点是否为黑色、任意一个红色节点的父节点不是红色、任意节点到根节点的路径上黑色节点的数量相等。
其中检测任意节点到根节点的路径上黑色节点数量相等可以用递归处理,先算出某条路径上黑色节点的数量,通过递归与每条路径上黑色节点的数量最对比,如果与某条路径上黑色节点的数量不相等则红黑树异常。

bool IsValidRBTree()
{
	Node* pRoot = _root;
	if (nullptr == pRoot)
	{
		return true;
	}

	//检测根节点是否满足情况
	if (BLACK != pRoot->_col)
	{
		cout << "违反性质二:根节点必须为黑色" << endl;
		return false;
	}

	//获取任意一条路径中黑色节点的个数
	size_t blackCount = 0;
	Node* pcur = pRoot;
	while (pcur)
	{
		if (BLACK == pcur->_col)
			blackCount++;
		pcur = pcur->_left;
	}

	//检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return _IsValidRBTree(pRoot, k, blackCount);
}

bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
{
	//走到nullptr后,判断k和black是否相等
	if (nullptr == pRoot)
	{
		cout << k << endl; 
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}

	//统计黑色节点的个数
	if (BLACK == pRoot->_col)
	{
		k++;
	}

	//检测当前节点与其双亲是否都为红色
	Node* pParent = pRoot->_parent;
	if (pParent && RED == pParent->_col && RED == pRoot->_col)
	{
		cout << "违反性质三:没有连在一起的红色节点" << endl;
		return false;
	}

	return _IsValidRBTree(pRoot->_left, k, blackCount) &&
		_IsValidRBTree(pRoot->_right, k, blackCount);
}

五、AVL树和红黑树比较

  • 红黑树:
    • 平衡机制基于颜色和一系列规则(如节点颜色、红色节点的子节点颜色、路径上黑色节点数量等)
    • 允许节点之间的高度差超过1,但通过保证从根节点到叶节点的任意路径上黑色节点的数量相同来避免树的高度过大
    • 在插入或删除节点时,可能需要通过变色和少量的旋转操作来维持平衡
  • AVL树:
    • 平衡机制基于每个节点的左子树高度和右子树高度之差

    • 严格要求所有节点的平衡因子为-1、0或1,即左右子树高度差不超过1

    • 在插入或删除节点时,可能需要通过多次旋转操作来维持平衡,并更新每个节点的平衡因子

红黑树相对AVL树并没有那么严格地要求平衡,仅通过颜色控制最长路径中节点个数不会超过最短路径节点个数的两倍就行。

当AVL树不平衡时仅通过旋转来处理,红黑树不平衡时可以变色、变色+旋转处理,显然红黑树插入、删除过程更有优势,而AVL树查找过程更有优势。而set和map底层、Linux内核等都使用红黑树实现。


本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~

头像

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

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

相关文章

【算法】DFS系列之 FloodFill 算法

【ps】本篇有 7 道 leetcode OJ。 目录 一、算法简介 二、相关例题 1&#xff09;图像渲染 .1- 题目解析 .2- 代码编写 2&#xff09;岛屿数量 .1- 题目解析 .2- 代码编写 3&#xff09;岛屿的最大面积 .1- 题目解析 .2- 代码编写 4&#xff09;被围绕的区域 .1-…

allegro精确画圆形边框

1.显示原点位置&#xff1a; 2.class-subclass依次选择Board Geometry-Outline 3.菜单ADD---Circle,右侧option,依次设置如下&#xff0c;如图可设置为圆心&#xff08;0&#xff0c;0&#xff09;&#xff0c;半径为42mm的边框&#xff0c;不要忘了右键Done&#xff0c;完成绘…

海量数据去重的hash,布隆过滤器Bloom Filter,一致性hash

文章目录 一、⭐hash函数如何选取hash函数&#xff1f;冲突处理 二、⭐海量数据去重布隆过滤器**布隆过滤器介绍**布隆过滤如何应用 三、⭐一致性哈希基本原理&#xff1a; 四、⭐一道面试题 一、⭐hash函数 hash函数的作⽤&#xff1a;避免插⼊的时候字符串的⽐较&#xff0c…

怎么把店铺地址弄上高德地图?

随着互联网的高度普及&#xff0c;如今的营销方式也发生了巨大的变化。以前的商家要想提高店铺销售额&#xff0c;只需要在线下举办促销活动就可以吸引周边的顾客上门消费&#xff0c;但现在&#xff0c;商家如果想获取更多客流量&#xff0c;就必须在网上进行曝光&#xff0c;…

性能测试学习6:jmeter安装与基本配置/元件

一.JDK安装 官网&#xff1a;https://www.oracle.com/ 二.Jmeter安装 官网&#xff1a;http://jmeter.apache.org/download_jmeter.cgi 下载zip包&#xff0c;zip后缀那个才是Windows系统的jmeter 三.Jmeter工作目录介绍 四.Jmeter功能 1&#xff09;修改默认配置-汉化 2&am…

市场价值99的likeyun付费进群插件

市场价值99的likeyun付费进群插件 这款是引流宝的付费插件 要搭配引流宝使用 多模版 最近发现好多人再倒卖大佬的付费插件 为了减少这种情况&#xff0c;在征得大佬的同意下决定免费发出来 此版本是付费进群最新1.0版本 不提供后续更新服务&#xff0c;有能力的还是建议支持…

每日OJ题_牛客_乒乓球筐_哈希_C++_Java

目录 牛客_乒乓球筐_哈希 题目解析 C代码 Java代码 牛客_乒乓球筐_哈希 乒乓球筐__牛客网 (nowcoder.com) 描述&#xff1a; nowcoder有两盒&#xff08;A、B&#xff09;乒乓球&#xff0c;有红双喜的、有亚力亚的……现在他需要判别A盒是否包含了B盒中所有的种类&#…

深入理解 Spring Cache 的工作原理及集成其它第三方缓存

目录 1、Spring Cache 简介2、常用注解2.1、常用注解介绍2.2、常用注解的主要参数 3、缓存注解上 SPEL 表达式可使用的元数据4、入门案例4.1、引入依赖4.2、开启缓存功能4.3、使用缓存4.3.1、新建一个 UserServiceImpl4.3.2、新建一个 UserController 5、工作原理5.1、缓存自动…

Java建筑行业智能化管理系统源码,PC端、手机端、大屏端源码,智慧工地管理平台源码,智慧建设平台 智慧住建平台

智慧工地平台全套源码合作 智慧工地是指运用现代信息技术&#xff0c;如物联网&#xff08;IoT&#xff09;、大数据、人工智能&#xff08;AI&#xff09;、云计算、移动互联网等&#xff0c;对传统建筑工地进行智能化改造和管理的新型工地。它通过高度集成的系统和设备&#…

GeoCue与Xer Technologies合作推动无人机测绘技术革新

GeoCue与Xer Technologies合作推动无人机测绘技术革新 近期,LiDAR测绘硬件和软件开发商GeoCue与瑞士长航时混合动力无人机制造商Xer Technologies AG携手合作,成功将GeoCue的TrueView 720 LiDAR和图像传感器集成至Xer X8无人机平台。这一里程碑式的合作不仅标志着无人机测绘技…

Python手绘五星红旗,庆75周年

环境 pip install matplotlib pip install numpy 代码 import matplotlib.pyplot as plt import numpy as np# 中国国旗的标准尺寸比例是 3:2 width, height 300, 200 # 这里可以调整为任何满足3:2比例的尺寸# 创建一个新图形 fig, ax plt.subplots(figsize(width/100, h…

js 字符串下划线转驼峰 驼峰转下划线

一、下划线转驼峰 1、效果 2、示例 function underscoreToCamelCase(str) {return str.replace(/(_\w)/g, function(match) {return match[1].toUpperCase();}).replace(/^_/, ); }// 示例 let snakeCaseStr "hello_world"; let camelCaseStr underscoreToCamelC…

【Diffusion分割】Cold SegDiffusion:医学图像分割的扩散模型

Cold SegDiffusion: A novel diffusion model for medical image segmentation 摘要&#xff1a; 随着深度学习的发展&#xff0c;扩散模型在医学图像分割任务中表现出了卓越的性能。然而&#xff0c;传统的分割扩散模型通常采用随机高斯噪声生成分割掩膜&#xff0c;导致分割…

【华为HCIP实战课程七】OSPF邻居关系排错MTU问题,网络工程师

一、MTU MUT默认1500,最大传输单元,一致性检测 [R3-GigabitEthernet0/0/1]mtu 1503//更改R3的MTU为1503 查看R3和SW1之间的OSPF邻居关系正常: 默认华为设备没有开启MTU一致性检测! [R3-GigabitEthernet0/0/1]ospf mtu-enable //手动开启MTU检测 [SW1-Vlanif30]ospf mtu…

项目——超级马里奥——Day(2)

争取今天晚上能搞一半啊&#xff0c;啊啊啊啊&#xff0c;感觉事多的忙不过来 设计思路&#xff1a; 1&#xff09;创建并完成常量类 ------->一张图片的情况 先完成对图片的封装------>把图片加载一遍 &#xff08;老实说&#xff0c;我也不太知道为什么&#xff0…

Windows 开发工具使用技巧 QT使用安装和使用技巧 QT快捷键

一、QT配置 1. 安装 Qt 开发框架 1、下载 1、进入下载地址 下载地址1 (官方, 需注册账号)&#xff1a; https://www.qt.io/download下载地址2&#xff08;推荐&#xff09;&#xff1a; http://download.qt.io/http://download.qt.io/archive/qt/ &#xff08;或更直接的…

【C++前缀和】1878. 矩阵中最大的三个菱形和|1897

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode 1878. 矩阵中最大的三个菱形和 难度分&#xff1a;1897 给你一个 m x n 的整数矩阵 grid 。 菱形和 指的是 grid 中一个正菱形 边界 上的元素之和。本题中的…

ElasticSearch备考 -- Update by query Reindex

一、题目 有个索引task&#xff0c;里面的文档长这样 现在需要添加一个字段all&#xff0c;这个字段的值是以下 a、b、c、d字段的值连在一起 二、思考 需要把四个字段拼接到一起&#xff0c;组成一个新的字段&#xff0c;这个就需要脚本&#xff0c; 这里有两种方案&#xff…

ES 文件浏览器批量下载的链接

在 ES 文件浏览器上的网页上下载文件需要一个一个点击下载&#xff0c;这样非常麻烦&#xff0c;实际上文件在网页上都对应有一个真实的地址&#xff0c;这样通过 For 循环可以打印出下载地址&#xff0c;然后粘贴到迅雷中就可以批量下载了 http://10.0.0.47:5050/download?pa…