【数据结构】红黑树模拟实现

2023-11-14

一.红黑树底层原理

红黑树的底层可以看作是AVL树的变种。先前我们了解过AVL的模拟实现。avl对整棵树的控制还是非常严格的,因为高度差不能大于2,导致会经常发生旋转。旋转这个过程也会降低效率,所以为了整体的效率衍生出了红黑树。红黑树旋转的没有那么频繁。红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

红黑树的节点没有平衡因子,由颜色替代,红色和黑色。红黑树的实现是由四条规则实现的

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

当满足这四条规则时,这棵树自然而然就是红黑树了。因为每条路径的黑色节点数是相同的,所以不同路径的节点数的差别一定在于红色节点数的不同。同时又因为红色节点不相邻,所以一条路径上最少可以有0个黑色节点,最多可以有黑色节点数个红色节点。这就控制了有一条路径会比其他路径长出俩倍。

二.红黑树的节点结构

其实本质上和avl树的节点没有任何不同,就是把平衡因子去点换成颜色


enum Colour
{
	RED,
	BLACK
};

template <class T>
struct RBTreeNode
{
	RBTreeNode(const T& data = T())
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
	{
	}

	RBTreeNode<T>* _pLeft;
	RBTreeNode<T>* _pRight;
	RBTreeNode<T>* _pParent;
	T _data;
	Colour _col;  
};

三.红黑树的插入

由于节点都有颜色所以我们在插入新的节点的时候首先要确定新节点是什么颜色的。这里我们采取插入节点为红色的策略,因为插入红色节点相对简单些。当我们插入一个新节点后,我们需要判断当前树是否还满足规则。若当前根节点为空的话可以直接让当前节点作为根节点。若不为空的话则和avl树一样先找到应该插入的位置,构建插入节点并连接指针的指向。唯一的区别是处理颜色和是否需要旋转的条件

先说parent在grand左边的情况。此时包含三种情形

第一种:uncle存在为红。这种情况不需要进行旋转,只需要进行变色。假设新增节点是D,此时不满足红黑树条件,uncle节点(也就是C)存在且是红色,此时我们让父节点(也就是D)和uncle节点变黑,grandparent(A)变红。不断向上传递颜色直到到根节点位置。最后把根节点变黑。

 变色到最后是这个样子

 第二种:当uncle节点不存在或者uncle为黑色。且新增节点在parent左侧的时候。需要进行变色加单旋,假设新增节点是D,此时相当于新增在较高左子树的左边,我们需要进行一个右单旋

 旋转完成后在进行变色,把parent变黑(parent有可能成为根节点)把grand变红

 

 这样就满足红黑树的条件了

 第三种:当uncle节点不存在或者uncle为黑色。且新增节点在parent右侧的时候。需要进行一个双旋。新增节点为e

 我们可以先对D进行一个左单旋,此时变成了和情况二一样的情形,再对B进行一个右单旋

 最后把新增节点变黑grand节点变红

 就满足红黑树条件了

 上述三种情况是parent在grand的左测情况,同理还有parent在grand右侧的情况。旋转方式相反思路相同。

	pair<iterator, bool> Insert(const T& data)
	{
		KeyOfT kot;
		if (_root == nullptr)  //处理当树为空时
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root),true);
		}
		Node* parent = nullptr;
		Node* cur = _root;
		//寻找插入位置
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_pRight;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_pLeft;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		cur = new Node(data);  //找到位置插入节点
		cur->_col = RED;		//新插入的节点置为红色
		Node* newnode = cur;

		cur->_pParent = parent;  //调整新插入节点的两个指针指向。
		if (kot(parent->_data) < kot(data))
		{
			parent->_pRight = cur;
		}
		else if (kot(parent->_data) > kot(data))
		{
			parent->_pLeft = cur;
		}

		while (parent && parent->_col == RED)
		{
			Node* grandfater = parent->_pParent;   //保存祖父节点
			if (parent == grandfater->_pLeft)		//当前节点是祖父的左节点
			{
				Node* uncle = grandfater->_pRight;		//说明叔叔节点数祖父的右节点
				//uncle存在且为红色,继续向上变色
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;			//父亲和叔叔变黑,爷爷变红
					parent->_col = BLACK;
					grandfater->_col = RED;

					cur = grandfater;				//继续向上传递
					parent = cur->_pParent;			
				}
				//uncle不存在+uncle存在且为黑色  (需要进行变色+旋转,若遇到折线情况需要进行双旋)
				else
				{
					if (cur == parent->_pLeft)   //相当于新增节点在较高左子树的左边,右单旋即可
					{
						RotateR(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else   //相当于新增节点在较高左子树的右侧,形成折线需要进行双旋转,先左旋在右旋
					{
						RotateL(parent);
						RotateR(grandfater);
						grandfater->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
			else if (parent == grandfater->_pRight)
			{
				Node* uncle = grandfater->_pLeft;
				//uncle存在且为红色,继续向上变色
				if (uncle && uncle->_col == RED)
				{
					uncle->_col = BLACK;			//父亲和叔叔变黑,爷爷变红
					parent->_col = BLACK;
					grandfater->_col = RED;

					cur = grandfater;
					parent = cur->_pParent;
				}
				//uncle不存在+uncle存在且为黑色  (需要进行变色+旋转,若遇到折线情况需要进行双旋)
				else
				{
					if (cur == parent->_pRight) 
					{
						RotateL(grandfater);
						grandfater->_col = RED;
						parent->_col = BLACK;
					}
					else   
					{
						RotateR(parent);			 //当前情况相当于新增节点在较高右子树的左侧
						RotateL(grandfater);
						grandfater->_col = RED;
						cur->_col = BLACK;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;   //最后把根节点的颜色赋值成黑色
		return make_pair(iterator(newnode), true);  //不能直接使用cur,cur有可能已经被更改
	}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【数据结构】红黑树模拟实现 的相关文章

  • 带头双向循环链表:一种高效的数据结构

    博客主页 江池俊的博客 收录专栏 数据结构探索 专栏推荐 cpolar C语言进阶之路 代码仓库 江池俊的代码仓库 编译环境 Visual Studio 2022 欢迎大家点赞 评论 收藏 文章目录 一 带头循环双向链表的概念及结构 二 使
  • 算法题-简单系列-05-两个链表的第一个公共结点

    文章目录 1 题目 1 1 思路1 循环遍历 1 题目 输入两个无环的单向链表 找出它们的第一个公共结点 如果没有公共节点则返回空 1 1 思路1 循环遍历 使用两个指针N1 N2 一个从链表1的头节点开始遍历 我们记为N1 一个从链表2的
  • 二叉树先中后序遍历-12.4(day12)

    二叉树三种基本遍历方式 先序遍历 从根节点开始 逐级向下遍历 中序遍历 从左子树最后一层的最左侧节点开始遍历 当遍历到根节点后在开始遍历右子树 后续遍历 先访问叶子节点 从左子树到右子树 最后到根节点 记忆方法 先 中 后可以理解为前 中
  • 牛客网(二叉树)

    这个题目和leetcode比起来就是有一些不一样 需要我们自己来写接口函数 所以有一些麻烦 我们得写一个中序遍历的函数做最后的输出 也得写一个函数来存储字符进去 还得写一个接口函数来创造节点 这个题目就和二叉树如何创造节点很相似 我们一个一
  • C/C++查找算法-----------------------二分查找详解

    二分查找 定义 实例 定义 二分查找也称折半查找 搜索过程从数组的中间元素开始 如果中间元素正好是要查找的元素 则搜索过程结束 如果某一特定元素大于或者小于中间元素 则在数组大于或小于中间元素的那一半中查找 而且跟开始一样从中间元素开始比较
  • C语言之变量的存储方式和生存周期

    一 变量的存储方式 C语言变量的存储有两种方式 静态存储方式和动态存储方式 相应的生产期也有两种 静态生存期和自动生存期 静态存储方式 在程序运行前为变量内存分配内存 在程序结束后回收变量的内存 静态生存期 动态存储方式 在程序运行过程中
  • 每日一练2023.12.17——大笨钟的心情【PTA】

    题目链接 L1 077 大笨钟的心情 题目要求 有网友问 未来还会有更多大笨钟题吗 笨钟回复说 看心情 本题就请你替大笨钟写一个程序 根据心情自动输出回答 输入格式 输入在一行中给出 24 个 0 100 区间内的整数 依次代表大笨钟在一天
  • LeetCode326. Power of Three

    文章目录 一 题目 二 题解 一 题目 Given an integer n return true if it is a power of three Otherwise return false An integer n is a po
  • 力扣每日一题:162. 寻找峰值(2023-12-18)

    力扣每日一题 题目 162 寻找峰值 日期 2023 12 18 用时 10 m 9 s 时间 0 ms 内存 40 54 MB 代码 class Solution public int findPeakElement int nums i
  • 【数据结构和算法】 K 和数对的最大数目

    其他系列文章导航 Java基础合集 数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一 题目描述 二 题解 2 1 方法一 双指针排序 三 代码 3 1 方法一 双指针排序 3
  • 数据结构算法-快速排序

    核心思路 快速排序算法核心思路 选择一个 基准 元素 将数组分为两个子数组 一个包含比基准小的元素 另一个包含比基准大的元素 然后对这两个子数组进行递归排序 基准数 初始化两个索引 i 和 j 分别子数组的开头和结尾 初始化基准元素 bas
  • 代码随想录算法训练营第42天| ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集

    416 分割等和子集 已解答 中等 相关标签 相关企业 给你一个 只包含正整数 的 非空 数组 nums 请你判断是否可以将这个数组分割成两个子集 使得两个子集的元素和相等 示例 1 输入 nums 1 5 11 5 输出 true 解释
  • 回溯算法第零篇【递归、搜索与回溯】【回溯算法入门必看】

    本篇文章的目的 1 给小伙伴们对回溯算法中的名词进行解释 2 消除递归的恐惧 回溯是递归的一个分支 给小伙伴们一个建议 整篇文章都要看完 一字不漏 全是干货 注意 分析回溯的思想之前 我们得知道一个关系 递归包含搜索 搜索包含回溯 所以我们
  • 【华为OD】给定一个整数数组nums,请你在该数组中找出两个数,使得这两个数 的和的绝对值abs(nums[x] + nums[y])为最小值并按从小到大返回这 两个数以及它们和的绝对值

    题目描述 给定一个整数数组nums 请你在该数组中找出两个数 使得这两个数 的和的绝对值abs nums x nums y 为最小值并按从小到大返回这 两个数以及它们和的绝对值 每种输入只会对应一个答案 数组中同一 个元素不能使用两遍 输入
  • 面试150-13(Leetcode238除自身以外数组的乘积)

    代码 class Solution public int productExceptSelf int nums int n nums length int res new int n int product 1 int zerocnt 0
  • 数据结构算法-快速排序

    核心思路 快速排序算法核心思路 选择一个 基准 元素 将数组分为两个子数组 一个包含比基准小的元素 另一个包含比基准大的元素 然后对这两个子数组进行递归排序 基准数 初始化两个索引 i 和 j 分别子数组的开头和结尾 初始化基准元素 bas
  • LeetCode21. Merge Two Sorted Lists

    文章目录 一 题目 二 题解 一 题目 You are given the heads of two sorted linked lists list1 and list2 Merge the two lists into one sort
  • LeetCode 2397. 被列覆盖的最多行数,状态压缩优化回溯法

    一 题目 1 题目描述 给你一个下标从 0 开始 大小为 m x n 的二进制矩阵 matrix 另给你一个整数 numSelect 表示你必须从 matrix 中选择的 不同 列的数量 如果一行中所有的 1 都被你选中的列所覆盖 则认为这
  • 排序:计数排序

    一 概念 计数排序是非比较排序 是对哈希直接定址法的变形应用 二 思想 利用数组统计相同数据出现的次数 例如整型数据m出现n次 就在数组m位置记录数据为n 最后从头遍历数组打印数据即可 通俗来讲就是 数组下标即为数据 下标所指位置的值即为数
  • 高精度运算合集,加减乘除,快速幂,详细代码,OJ链接

    文章目录 零 前言 一 加法 高精度加法步骤 P1601 A B 二 减法 高精度减法步骤

随机推荐

  • BZOJ4817 [Sdoi2017]树点涂色(洛谷P3703)

    LCT 线段树 BZOJ题目传送门 洛谷题目传送门 码力不行啊 操作1就是access啦 操作2就是 w x w y 2 w lca x y 1 w x w y 2
  • 利用Libev写一个简单的client和server程序

    利用Libev写一个简单的client和server程序 common h程序 ifndef COMMON H define COMMON H include
  • 15 移动端app自动化测试 - 软件测试

    软件测试所有内容笔记正在陆续更新中 笔记已经在本地记录 全部为自己手动记录的笔记及总结 正在开始更新中 后续会逐步更新并完善到 软件测试学习内容总结 专栏 本节内容 移动端app自动化测试 文章目录 1 appium环境安装与架构介绍 1
  • linux常用命令:文本编辑

    目录 三 文本编辑 1 vim三种工作模式 2 常用命令 3 插入文本快捷键 4 查找文本快捷键 5 查找文本效果图 6 替换文本快捷键 7 删除文本快捷键 8 复制和粘贴文本快捷键 9 保存退出文本命令 10 参考文章 三 文本编辑 一般
  • 二值分类模型的评价指标

    二值分类模型的评价指标主要有 Precision Recall F Score ROC and AUC ROC Receiver Operating Characteristic ROC曲线的横坐标为false positive rate
  • linux gsoap服务端,gsoap linux下服务器端代码生成与测试

    二 解压压缩包 ios 三 进入cd gSoap gsoap 2 8 gsoap bin linux386函数 四 建立一个头文件serverTest h测试 gsoap ns service name calc gsoap ns serv
  • Python selenium错误:ElementNotInteractableException: Message: element not interactable: Element is not

    脚本没问题 但是执行时报错 selenium common exceptions ElementNotInteractableException Message element not interactable Element is not
  • [Python图像处理] 五.图像融合、加法运算及图像类型转换

    该系列文章是讲解Python OpenCV图像处理知识 前期主要讲解图像入门 OpenCV基础用法 中期讲解图像处理的各种算法 包括图像锐化算子 图像增强技术 图像分割等 后期结合深度学习研究图像识别 图像分类应用 希望文章对您有所帮助 如
  • javascript的深拷贝和浅拷贝

    当我们将一个变量的值赋给另外一个变量时 需要注意用的是深拷贝还是浅拷贝 深拷贝是另外开辟地址空间 浅拷贝是和被拷贝数据地址一样 详见 https github com YvetteLau Step By Step issues 17
  • element-ui 的confirm用法

    在点击函数里变嵌入一下代码 this confirm 确认删除 系统提示 confirmButtonText 确定 cancelButtonText 取消 cancelButtonClass btn custom cancel type w
  • 解决websocket使用@Autowired、@Value获取值为null解决方法

    解决webSocker中使用 Value获取配置文件中值为null 1 常规写法 在webSocker中使用 Value 取值为null 2 原因分析 3 解决问题 1 常规写法 在webSocker中使用 Value 取值为null Co
  • 浏览器渲染原理

    页面渲染流程 浏览器收到web网页的内容后会开始下载 HTML CSS JS 并绘制 DOM 树和 CSS 树 当两颗树都绘制完成后会合并成一个渲染树 然后再经过Layout Paint Composite 最终完成渲染 HTML 解析会被
  • hortonworks/registry配置详解

    1 美图 2 配置 deploy demo db registry cat conf registry yaml registries configuration modules name schema registry className
  • 什么是CSRF攻击,以及如何防御

    什么是CSRF攻击 以及如何防御 1 CSRF攻击的概念 2 CSRF攻击简单案例 2 1 银行网站项目 2 2 危险网站的项目 2 3 测试 3 默认的CSRF防御策略 4前后端分离的CSRF防御策略 1 CSRF攻击的概念 1 CSRF
  • MPI群通信与矩阵乘法的Fox算法实现

    原本以为 MPI天生只能在Linux上运行 但这次却发现了Intel MPI Library 这个好用的东西 基本不需要设置 安上之后 用自己能登录windows的帐号和密码注册就行了 虽然不是局域网上的机器 但也可以让我的双核CPU达到1
  • WebSocket协议之NGINX代理转发无法建立连接问题处理

    WebScoket协议如需要通过nginx代理 需要location 节点增加以下节点即可正常建立连接 需要配置以下节点 proxy http version 1 1 proxy set header Upgrade http upgrad
  • Python入门网络爬虫之精华版,赶快收藏

    相关文件 关注小编 私信小编领取哟 当然别忘了一件三连哟 公众号 Python日志 前言 Python学习网络爬虫主要分3个大的版块 抓取 分析 存储 另外 比较常用的爬虫框架Scrapy 这里最后也详细介绍一下 举例子 当我们在浏览器中输
  • STL 中集合操作相关算法

    merge 头文件 merge 算法定义在头文件 include 中 算法作用 merge 算法是合并两个有序的序列 合并结果拷贝到一个新的序列 前提是这两个序列的排序规则一样 代码示例 vector
  • vue---UI框架elementUI实现系统登录注册页

    https blog csdn net maidu xbd article details 87943243已经搭建好了vue开发环境 在本博客中 来介绍些结合element ui实现登录注册界面 界面效果展示如下图 实现的功能包括 首先安
  • 【数据结构】红黑树模拟实现

    一 红黑树底层原理 红黑树的底层可以看作是AVL树的变种 先前我们了解过AVL的模拟实现 avl对整棵树的控制还是非常严格的 因为高度差不能大于2 导致会经常发生旋转 旋转这个过程也会降低效率 所以为了整体的效率衍生出了红黑树 红黑树旋转的