【数据结构】简单快速过一遍红黑树

news2024/11/19 0:34:39

文章目录

  • 红黑树
    • 1 红黑树的概念
    • 2 红黑树的性质
    • 3 红黑树节点的定义
    • 4 红黑树的插入操作
    • 5 红黑树的验证
    • 6 红黑树与AVL树的比较
    • 7.C++实现红黑树


红黑树

1 红黑树的概念

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

image-20230404133219686

2 红黑树的性质

  1. 每个结点不是红色就是黑色

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点

  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

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

  • 举个极端一点的例子就清楚了,树的一边全是黑节点,另一边是一黑一红交替,那么全黑的那一边一定是最短的那个,一黑一红交替一定是最长的那个,这时候刚好是2倍关系

image-20230404171718935


3 红黑树节点的定义

enum Color
{
	RED,//red红色
	BLACK,//black黑色
};

template<class T>
struct RBTreeNode
{
	//在节点的定义中,为什么要将节点的默认颜色给成红色的?---因为这样违反的红黑树规则最少,方便处理
	RBTreeNode(const T& val,Color color=RED)
		:_val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _color(color)
	{}

	T _val;// 节点的值
	RBTreeNode<T>* _left;// 节点的左孩子
	RBTreeNode<T>* _right;// 节点的右孩子
	RBTreeNode<T>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
	Color _color;// 节点的颜色
};

4 红黑树的插入操作

红黑树同样也是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点
  2. 检测新节点插入后,红黑树的性质是否造到破坏
    因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何
    性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连
    在一起的红色节点,此时需要对红黑树分情况来讨论:
    约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
    • 情况一: cur为红,p为红,g为黑,u存在且为红 ----p(parent),g(grandparent),u(uncle)

image-20230404194800048

cur和p均为红,违反了性质三,此处能否将p直接改为黑?—不能
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

  • 情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑

image-20230404200017733

  • 情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

image-20230404200756753


5 红黑树的验证

红黑树跟AVL树的代码一样复杂,那我们怎么知道自己写的代码有没有问题呢?

同样也可以写一个小代码来检验一下是否符合红黑树性质即可

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)

  2. 检测其是否满足红黑树的性质

bool IsValidRBTree()
{
	if (_root == nullptr)//空树也是红黑树
		return true;
	if (_root->_color != BLACK)
	{
		cout << "违反了红黑树性质二:根节点必须为黑色" << endl;
		return false;
	}
	//获取任意一个节点的黑色节点
	size_t blackCount = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_color == BLACK)
			blackCount++;
		cur = cur->_left;
	}
	// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
	size_t k = 0;
	return IsValidRBTree(_root, blackCount, k);
}
bool IsValidRBTree(Node* root, size_t blackCount, size_t k)
{
	//走到null之后,判断k和black是否相等
	if (root == nullptr)
	{
		if (k != blackCount)
		{
			cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
			return false;
		}
		return true;
	}
	// 统计黑色节点的个数
	if (root->_color == BLACK)
		k++;
	// 检测当前节点与其双亲是否都为红色
	Node* parent = root->_parent;
	if (parent && parent->_color == RED&& root->_color == RED)
	{
		cout << "违反性质三:不能存在连在一起的红色节点" << endl;
		return false;
	}
	return IsValidRBTree(root->_left, blackCount, k) &&
		IsValidRBTree(root->_right, blackCount, k);
}


6 红黑树与AVL树的比较

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

总之一句话:

实际应用中,若搜索的次数远远大于插入和删除,那么选择AVL,如果搜索,插入删除次数几乎差不多,应该选择红黑树。

AVL树传送门


7.C++实现红黑树

#pragma once 
#include <iostream>
#include <ctime>
using namespace std;

namespace hdm
{
	enum Color
	{
		RED,//red红色
		BLACK,//black黑色
	};

	template<class T>
	struct RBTreeNode
	{
		//在节点的定义中,为什么要将节点的默认颜色给成红色的?---因为这样违反的红黑树规则最少,方便处理
		RBTreeNode(const T& val, Color color = RED)
			:_val(val), _left(nullptr), _right(nullptr), _parent(nullptr), _color(color)
		{}

		T _val;// 节点的值
		RBTreeNode<T>* _left;// 节点的左孩子
		RBTreeNode<T>* _right;// 节点的右孩子
		RBTreeNode<T>* _parent;// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
		Color _color;// 节点的颜色
	};
	


	template<class T>
	class RBTree
	{
	public:
		typedef RBTreeNode<T> Node;

		bool Insert(const T& x)
		{
			if (_root == nullptr)
			{
				_root = new Node(x);
				_root->_color = BLACK;//规定根节点都是黑色
				return true;
			}

			Node* cur = _root;
			Node* parent = cur;//记录parent方便后续插入
			while (cur)
			{
				//cur往子树走的同时记录子树的父节点
				if (cur->_val > x)//比cur小往左子树找
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_val < x)//比cur大往右子树找
				{
					parent = cur;
					cur = cur->_right;
				}
				else//进入else说明cur->_val==x,表示已经存在该节点,不需要插入,返回false表示插入失败
				{
					return false;
				}
			}

			//程序走到这说明cur=nullptr,最后x就应该要插入在parent的下面,至于是插入左边还是右边
			//具体看x与parent->_val 之间值的关系
			cur = new Node(x);
			cur->_color = RED;//默认插入红色节点
			if (parent->_val > x)//如果x的值小于parent就往左边插入
			{
				parent->_left = cur;
			}
			else//如果x的值大于parent就往右边插入
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//判断是否符合红黑树规则,没有则要改颜色
			while (parent&& parent->_color == RED)
			{
				//大致分两个大种情况:
				//1.叔叔节点存在且为红---看图
				//2.叔叔节点不存在或者存在且为黑
				
				Node* grandparent = parent->_parent;
				if (grandparent->_left == parent)//确定叔叔节点的位置
				{
					Node* uncle = grandparent->_right;
					if (uncle && uncle->_color == RED)
					{
						//情况一:叔叔节点存在且为红
						//处理方式:变色处理
						parent->_color = uncle->_color = BLACK;
						grandparent->_color = RED;

						//这种情况因为改了祖父节点的颜色有可能影响到其他节点,所以要继续向上调整
						cur = grandparent;
						parent = cur->_parent;
					}
					else
					{
						//有可能是uncle节点不存在,也有可能是uncle是黑节点---对应就是情况二和三
						
						//uncle节点可能不存在或者为黑节点
						if (parent->_left == cur)//cur与parent是同侧
						{
							//右旋+变色
							RotateR(grandparent);
							parent->_color = BLACK;
							grandparent->_color = RED;
						}
						else//cur和parent不是同一侧--情况三
						{
							//左右旋+变色
							RotateL(parent);
							RotateR(grandparent);
							cur->_color = BLACK;
							grandparent->_color = RED;
						}
						break;
					}
				}
				else//grandparent->_right == parent---其实跟上面的情况类似,只不过方向相反
				{
					Node*uncle = grandparent->_left;
					if (uncle&& uncle->_color == RED)//情况一:叔叔节点存在且为空
					{
						uncle->_color = parent->_color = BLACK;
						grandparent->_color = RED;

						//继续向上调整
						cur = grandparent;
						parent = cur->_parent;
					}
					else
					{
						//可能是情况二或者三

						if (parent->_right == cur)//当cur与parent是同侧的时候
						{
							//左旋+变色
							RotateL(grandparent);
							parent->_color = BLACK;
							grandparent->_color = RED;
						}
						else//不同侧
						{
							//右左旋转+变色
							RotateR(parent);
							RotateL(grandparent);
							cur->_color = BLACK;
							grandparent->_color = RED;
						}
						break;
					}
				}	
			}

			_root->_color = BLACK;//因为上面的操作有可能把跟节点变红,不管怎么样直接加这句代码就不用管它不可能出现根是红的情况

			return true;
		}
		void InorderTree()
		{
			InorderTree(_root);
			cout << endl;
		}

		bool IsValidRBTree()
		{
			if (_root == nullptr)//空树也是红黑树
				return true;
			if (_root->_color != BLACK)
			{
				cout << "违反了红黑树性质二:根节点必须为黑色" << endl;
				return false;
			}
			//获取任意一个节点的黑色节点
			size_t blackCount = 0;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_color == BLACK)
					blackCount++;
				cur = cur->_left;
			}
			// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数
			size_t k = 0;
			return IsValidRBTree(_root,blackCount,k);
		}

	private:

		bool IsValidRBTree(Node* root,size_t blackCount,size_t k)
		{
			//走到null之后,判断k和black是否相等
			if (root == nullptr)
			{
				if (k != blackCount)
				{
					cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
					return false;
				}
				return true;
			}
			// 统计黑色节点的个数
			if (root->_color == BLACK)
				k++;
			// 检测当前节点与其双亲是否都为红色
			Node* parent = root->_parent;
			if (parent && parent->_color == RED&& root->_color == RED)
			{
				cout << "违反性质三:不能存在连在一起的红色节点" << endl;
				return false;
			}
			
			return IsValidRBTree(root->_left,blackCount,k) && 
					IsValidRBTree(root->_right,blackCount,k);
		}

		void InorderTree(Node* root)
		{
			if (root == nullptr)
				return;
			InorderTree(root->_left);
			cout << root->_val << " ";
			InorderTree(root->_right);
		}
		void RotateL(Node* parent)//左旋
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;

			Node* pparent = parent->_parent;
			if (subRL)//如果存在subRL,就把它的父子关系连上
				subRL->_parent = parent;
			parent->_right = subRL;
			subR->_left = parent;
			parent->_parent = subR;

			if (pparent == nullptr)//pparent为空表示parent为根节点
			{
				_root = subR;
				_root->_parent = nullptr;
			}
			else
			{
				//pparent存在,要判断parent之前是位置pparent的那一边
				if (pparent->_left == parent)
				{
					pparent->_left = subR;
				}
				else
				{
					pparent->_right = subR;
				}
			}
			subR->_parent = pparent;
		}

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

			Node* pparent = parent->_parent;

			if (subLR)//如果存在subLR,就把它的父子关系连上
				subLR->_parent = parent;
			parent->_left = subLR;
			subL->_right = parent;
			parent->_parent = subL;
			if (pparent == nullptr)//说明parent是根节点
			{
				_root = subL;
				_root->_parent = nullptr;
			}
			else
			{
				//pparent存在,要判断parent之前是位置pparent的那一边
				if (pparent->_left == parent)
				{
					pparent->_left = subL;
				}
				else
				{
					pparent->_right = subL;
				}
			}
			subL->_parent = pparent;
		}
	private:
		Node* _root=nullptr;
	};


	void RBTreeTest1()
	{
		int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
		//	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
		//	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
		RBTree<int> t;
		for (auto e : a)
		{
			if (e == 13)
				int i = 0;
			t.Insert(e);
		}
		t.InorderTree();
	}


	void RBTreeTest2()
	{
		srand(time(0));
		const size_t N = 10000;
		RBTree<int> t;
		for (size_t i = 0; i < N; ++i)//用1w个随机数测试插入是否符合红黑树规则
		{
			size_t x = rand();
			t.Insert(x);
			//cout << t.IsBalance() << endl;
		}

		t.InorderTree();

		cout << t.IsValidRBTree() << endl;
	}
}

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

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

相关文章

记一次oracle入库慢,log file switch (checkpoint incomplete)

AWR报告生成&#xff1a;Oracle AWR报告生成步骤_小百菜的博客-CSDN博客 发现log file switch (checkpoint incomplete) 这里出现了大量的log file switch(checkpoint incomplete)等待事件。 查看redo每个组的大小、状态 select group#,thread#,archived,status, bytes/102…

Python数据结构-----非递归实现快速排序

目录 前言&#xff1a; 非递归快排 1.概念原理 2.示例 Python代码实现 非递归快速排序 前言&#xff1a; 上一期我们学习了通过递归来实现快速排序的方法&#xff0c;那这一期我们就来一起学习怎么去通过非递归的方法来去实现快速排序的功能。&#xff08;上一期连接Pytho…

新来一00后,给我卷崩溃了..

2022年已经结束结束了&#xff0c;最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;相信很多小伙伴也在准备今年的金三银四的面试计划。 在此展示一套学习笔记 / 面试手册&#xff0c;年后跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的…

SLIC超像素分割算法

SLIC超像素分割算法 《SLIC Superpixels》 摘要 超像素在计算机视觉应用中越来越受欢迎。然而&#xff0c;很少有算法能够输出所需数量的规则、紧凑的超级像素&#xff0c;并且计算开销低。我们介绍了一种新的算法&#xff0c;将像素聚类在组合的五维颜色和图像平面空间中&a…

大四的告诫

&#x1f442; LOCK OUT - $atori Zoom/KALONO - 单曲 - 网易云音乐 &#x1f442; 喝了一口星光酒&#xff08;我只想爱爱爱爱你一万年&#xff09; - 木小雅 - 单曲 - 网易云音乐 其实不是很希望这篇文章火&#xff0c;不然就更卷了。。 从大一开始&#xff0c;每天10小时…

ccf b类及以上会议(准备)

SoCCACM Symposium on Cloud Computing http://dblp.uni-trier.de/db/conf/cloud/ SimLess: simulate serverless workflows and their twins and siblings in federated FaaS.Pisces: efficient federated learning via guided asynchronous training论文截止时间&#xff1a…

实现自定义dialog样式

1定义弹出的dialog样式 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"a…

3. 排序

3. 排序 3.1 总纲 3.2 Comparable与Comparator接口介绍 由于我们这里要讲排序&#xff0c;所以肯定会在元素之间进行比较。规则的。在实际应用中&#xff0c;我们往往有需要比较两个自定义对象大小的地方。而这些自定义对象的比较&#xff0c;就不像简单的整型数据那么简单&a…

Python轻量级Web框架Flask(7)——翻页功能/多表操作

1、使用paginate实现分页&#xff1a; 基础指令&#xff08;新建对象&#xff09; 基础指令&#xff08;使用对象属性&#xff09; 2、几种类型的表操作&#xff1a; 一对一&#xff1a;例如一个人只能有一张身份证。一对多&#xff1a;例如班级和学生&#xff08;一个班级…

苦中作乐 ---竞赛刷题 完结篇(25分)

&#xff08;一&#xff09;目录 L2-014 列车调度 L2-024 部落 L2-033 简单计算器 L2-042 老板的作息表 L2-041 插松枝 &#xff08;二&#xff09;题目 L2-014 列车调度 火车站的列车调度铁轨的结构如下图所示。 两端分别是一条入口&#xff08;Entrance&#xff09;轨道…

Java分布式事务(十二)

文章目录 🔥Hmily实现TCC分布式事务_项目搭建🔥Hmily实现TCC分布式事务实战_公共模块🔥Hmily实现TCC分布式事务_集成Dubbo框架🔥Hmily实现TCC分布式事务_项目搭建 创建父工程tx-tcc 设置逻辑工程 <packaging>pom</packaging>创建公共模块 创建转出银行…

分析 | 通过 NFTScan 率先捕获 NFT 投资趋势

NFT 市场信息高度动态且机会稍纵即逝&#xff0c;了解市场第一信息对于 NFT 的参与者来说都是至关重要的。所以市场主体参与者必须密切关注各种渠道&#xff0c;努力获取最新一手 NFT 信息&#xff0c;这对参与者抓住先机和获益至关关键&#xff0c;若信息滞后&#xff0c;容易…

【流畅的Python学习笔记】2023.4.21

此栏目记录我学习《流畅的Python》一书的学习笔记&#xff0c;这是一个自用笔记&#xff0c;所以写的比较随意 特殊方法&#xff08;魔术方法&#xff09; 不管在哪种框架下写程序&#xff0c;都会花费大量时间去实现那些会被框架本身调用的方法&#xff0c;Python 也不例外。…

【Python】matplotlib设置图片边缘距离和plt.lengend图例放在图像的外侧

一、问题提出 我有这样一串代码&#xff1a; import matplotlib.pyplot as plt plt.figure(figsize (10, 6)) " 此处省略代码 " legend.append("J") plt.legend(legend) plt.xlabel(recall) plt.ylabel(precision) plt.grid() plt.show()我们得到的图像…

KMP算法原理原来这么简单

我觉得这句话说的很好&#xff1a; kmp算法关键在于&#xff1a;在当前对文本串和模式串检索的过程中&#xff0c;若出现了不匹配&#xff0c;如何充分利用已经匹配的部分&#xff0c;来继续接下来的检索。 暴力解决字符串匹配 暴力解法就是两层for循环,每次都一对一的匹配&…

面试官:“请描述一下Android系统的启动流程”

作者&#xff1a;OpenGL 前言 什么是Android启动流程呢&#xff1f;其实指的就是我们Android系统从按下电源到显示界面的整个过程。 当我们把手机充好电&#xff0c;按下电源&#xff0c;手机会弹出相应启动界面&#xff0c;在等了一段时间之后&#xff0c;会弹出我们熟悉的主…

AI数据标注工程师这个职业怎么样?

本篇文章主要讲解ai数据标注工程师这个职业的具体情况和相关的职业前景 作者&#xff1a;任聪聪 日期&#xff1a;2023年4月18日 数据是ai的灵魂&#xff0c;自然界中相对应的数据都活多少存在不准确、杂乱、无效等属性&#xff0c;需要人为进行收集、整理、分类和处理。其中ai…

Linux 内核原理摘录

文章目录 一、Linux 内核设计与实现1、进程管理&#xff08;1&#xff09;调度2、内核数据结构&#xff08;1&#xff09;kfifo 3、中断 一、Linux 内核设计与实现 本章主要用来摘录《Linux 内核设计与实现》一书中学习知识点&#xff0c;其基于 Linux 2.6.34 。 1、进程管理 …

PowerShell install go+caddy+filebrowser+nssm 实现部署文件系统

filebrowser filebrowser 是一个使用go语言编写的软件&#xff0c;功能是可以通过浏览器对服务器上的文件进行管理。可以是修改文件&#xff0c;或者是添加删除文件&#xff0c;甚至可以分享文件&#xff0c;是一个很棒的文件管理器&#xff0c;你甚至可以当成一个网盘来使用。…

找高清视频素材,就上这6个网站。

推荐6个高清视频素材库&#xff0c;免费下载&#xff0c;建议收藏~ 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYwNDUx 菜鸟图库可以找到设计、办公、图片、视频、音频等各种素材。视频素材就有上千个&#xff0c;全部都很高清&#xff0c;站内可以按标签分类查找…