【数据结构进阶】红黑树超详解 + 实现(附源码)

news2025/1/24 10:16:23
🌟🌟作者主页:ephemerals__
🌟🌟所属专栏:数据结构

目录

前言

一、红黑树介绍

二、红黑树原理详解

三、红黑树的实现

1. 节点定义

2. 红黑树类型定义及接口声明

3. 红黑树的插入(重点)

颜色设置

平衡调整

总代码 

4. 红黑树的查找

5. 中序遍历、拷贝构造和析构

6. 检查红黑树是否合法

7. 程序全部代码

总结


前言

        在传统二叉搜索树的基础上,我们学习了AVL树,它通过独特的平衡机制,确保了稳定高效的插入、查找和删除操作。然而,由于其频繁的平衡调整,可能使性能收到一定影响。因此,另一种自平衡二叉搜索树——红黑树应运而生。本篇文章,我们将深入探讨红黑树的实现原理,带你解开其简洁而深邃的设计之美。

        与AVL树相同,之后的红黑树实现当中,我们会将键值对(pair)作为节点数据域。

        如果你不是很了解二叉搜索树、AVL树或pair,可以参阅这两篇文章:

【数据结构】二叉搜索树(二叉排序树)-CSDN博客

【数据结构进阶】AVL树深度剖析 + 实现(附源码)-CSDN博客

        注:若无特殊说明,本文所提到的所有“路径”都指根节点到NULL的路径。

一、红黑树介绍

        红黑树(Red-Black-Tree)是一种自平衡二叉搜索树,但它并非像AVL树那样“严格平衡”,而是允许一定的不平衡存在,在保证增删查改效率没有太大影响的情况下,显著减少了平衡调整的次数,提升总体效率。

        AVL树一般通过节点的“平衡因子”来维持平衡,而红黑树通过给节点“着色”,确保其高效性。

在非空情况下,红黑树的性质(约束条件)如下:

1. 它是一棵二叉搜索树

2. 每一个节点都会被着色,不是黑色就是红色

3. 根节点必须为黑色

4. 对于一个红色节点,它的孩子或为空,或是黑色。也就是说路径上不能有连续红色节点

5. 从根节点到NULL节点的所有路径上黑色节点的数量都相同

有了以上约束条件,就可确保其没有一条路径长度能够超出其他路径的2倍,从而保证高效操作。 

如上图,每一条路径上都有相同数量的黑色节点。 

二、红黑树原理详解

        那么,红黑树为什么能够控制路径长度呢?

        来看一个极端情况:

        假设一棵红黑树的一条路径上有n个黑色节点,那么由于其所有路径上的黑色节点数量是相同的,所以其所有路径上都一定有n个黑色节点。对于这棵树,最长的可能路径上的节点就是一黑一红一黑一红(要确保无连续红色节点)......一共有2n个节点最短的可能路径上的节点是全黑的,一共有n个节点那么其他可能的路径长度都在n~2n之间。所以说没有一条路径长度能够超出其他路径长度的两倍,也就确保了根节点左右子树的高度比一定在1~2之间

接下来,我们分析一下红黑树的效率。

        设一棵红黑树一共有N个节点,它的最短可能路径的长度h,由于可能的路径长度都在h~2h之间,那么节点数N就满足 2^{h}-1\leq N\leq 2^{2h}-1 。由此可知,在路径最短情况下,其进行增删查改的时间消耗为O(logN);路径最长情况下,进行查找的时间消耗为O(2logN)。因此红黑树增删查改的时间复杂度为O(logN)

        红黑树的平衡控制相对AVL树较为抽象,但由于那几点约束条件,控制了最长路径和最短路径之比,间接地使红黑树达到了“近似平衡”,增删查改地时间消耗不会过大,并且相比AVL树,旋转调整次数会更少。

三、红黑树的实现

1. 节点定义

        红黑树节点及其颜色定义如下:

enum Color//枚举值表示颜色
{
	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;//指向父亲的指针
	Color _col;//颜色

	RBTreeNode(const pair<K, V>& kv)//节点构造
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

与AVL树相同,红黑树也采用三叉链表结构,更有利于节点之间的控制访问。

在节点构造当中,我们并没有设置节点的初始颜色,之后在实现节点插入的实现当中,我们会重点讨论初始颜色的问题。

2. 红黑树类型定义及接口声明

        我们需要实现的接口如下:

//红黑树类
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//强制生成无参构造
	RBTree() = default;

	//拷贝构造
	RBTree(const RBTree<K, V>& t);

	//析构函数
	~RBTree();

	//插入
	bool Insert(const pair<K, V>& kv);

	//查找
	Node* Find(const K& key);

	//中序遍历
	void Inorder();

	//判断是否为合法红黑树
	bool IsValidRBTree();
private:
	Node* _root = nullptr;//根节点指针
};

3. 红黑树的插入(重点)

        为了保持红黑树增删查改的高效性,插入操作要保证满足红黑树的性质

        红黑树的插入过程大致如下:

1. 首先按照二叉搜索树的插入规则确定插入节点的位置。

2. 插入新节点,并确定新节点的颜色。

3. 为保持红黑树的性质,需要对原有树结构进行平衡调整


颜色设置

 我们首先来探讨新节点的颜色设置问题

        如果新插入的节点是根节点(树为空),毋庸置疑,为黑色。并且此时整个结构已经满足红黑树性质,插入完成。

        如果新插入的节点不是根节点,那么能否设置初始颜色为黑色呢?我们假设新插入的节点为黑色

不难发现,插入节点4之后,已经不满足红黑树性质5->2->4->NULL这条路径上有3个黑色节点,而5->7->6->NULL这条路径上有2个黑色节点实际上,如果插入黑色节点,不管插入到什么位置,该条路径上的黑色节点数都会增加,而其他路径上的黑色节点数不变,此时一定会违反红黑树的约束条件

那么,我们插入一个红色节点

此时可以看到,插入之后,整个结构依然满足红黑树的性质。当然,这是因为节点2是黑色节点,此时插入结束。若一个红色节点插入在红色节点的下方,出现连续红色节点,那么也会违反约束条件

总之,若插入黑色节点,一定违反红黑树规则,而插入红色节点,可能违反红黑树规则。所以我们退而求其次,将新节点(除根节点外)设置为红色


平衡调整

确定了新节点颜色之后,我们探讨平衡调整的问题

        之前已经提到,我们如果将新节点插入在红色节点的下方,就需要进行平衡调整。有两种情况需要分别讨论。

情况1:仅变色

如上图所示, parent和uncle都是红色,grandfather为黑色,此时插入新节点cur,出现连续红色节点。这种情况下,将parent和uncle变黑,grandfather变红,整个结构就满足了红黑树的性质

        但上图是一种具体情况,如果整棵树是另一棵树的子树,且节点4是红色的,那么变色之后就会再次出现连续的红色节点(节点2和节点4),此时就需要继续向上调整将grandfather作为新的cur,然后找到新的parent和uncle,进行变色或做其他调整(之后会讲解),然后再向上检查,直到遇到根节点或者无连续红色节点为止。

通过观察上图,不难发现:针对需要进行平衡调整的情况,新节点插入之后,parent一定为红,否则无需调整;grandfather一定为黑,否则未插入节点时就已经出现连续红色节点,红黑树原本就有问题。 那么,重要变量就是uncle节点

总结:当uncle为红时,仅需变色:将parent和uncle变黑,grandfather变红。然后将grandfather作为新的cur,找到新的parent和uncle,继续判断、调整,直到遇到根节点或无连续红色节点。

情况2:旋转+变色

        刚才提到uncle是重要变量,那么我们就给出uncle的其他可能情况(不存在或为黑),进而分析各种调整场景。

分类讨论之前,我们首先需要明确需要进行平衡调整的情况下,uncle和cur的关系

一个节点被标记为cur(确定插入位置之后),仅有两种情况:

1. 它是新增节点;2. 它是场景1向上调整时标记的节点


当uncle不存在时,cur一定是新增节点。原因:若cur不是新增节点,则其必然是向上调整时标记的节点,那么一定发生了变色,也就是说cur所在的某条路径A上至少有两个黑色节点(包括一个根节点)。而uncle不存在,则从根节点到uncle(NULL)位置的路径上的黑色节点数一定少于路径A,两条路径黑色节点数量不一致,不满足红黑树性质。

当uncle为黑时,cur一定时向上调整时标记的节点。原因:uncle为黑,则从根节点到uncle的路径B上至少有两个黑色节点。如果cur是新增节点,那么从根节点到cur的路径上的黑色节点数一定少于路径B,两条路径黑色节点数量不一致,不满足红黑树性质。

uncle不存在:

这种情况下,如果只是改变parent和grandfather的颜色,并不能解决问题:变色之后路径4->2->1->NULL上有1个黑色节点,而路径4->NULL上没有黑色节点,不满足红黑树性质

        此时就要配合旋转操作来解决问题。根据三个节点的相对位置,需要我们分情况进行单旋或双旋,从而调整树的结构:

单旋+变色:

可以看到,我们以grandfather为旋转点,进行右/左单旋,然后将parent变黑,grandfather变红,整个结构满足红黑树性质,并且该部分的根已经变成黑色,无需继续向上调整,插入结束

双旋+变色:

双旋完成后,将cur变黑,grandfather变红,整个结构满足红黑树,并且该部分的根已经变成黑色,无需继续向上调整,插入结束

总结:当uncle不存在时,需要根据实际情况进行旋转+变色。单旋完成后要将parent变黑,grandfather变红;双旋完成后要将cur变黑,grandfather变红。操作结束后,该部分的根成为黑色,不会出现连续红色节点,无需再向上调整。 

uncle为黑:

        刚才已经提到,uncle为黑时,cur一定是向上调整时标记的节点。所以我们使用抽象图来表示插入状况:

如图所示, a、b、c、d、e分别表示相应黑色节点数量的子树,当a、b的黑色节点数由n-1变为n时,说明发生了变色,使得cur变为红色。此时由于uncle是黑色,如果直接将parent变黑,grandfather变红,那么根节点到a、b、c路径上的黑色节点数与到d、e的路径不相等,不满足红黑树的性质。所以要以grandfather为旋转点,分情况进行旋转再变色

不难发现,uncle为黑时的旋转、变色逻辑与uncle不存在时完全相同,这里博主就不再一一列举各种旋转情况。总结:当uncle为黑时,需要根据实际情况进行旋转+变色。单旋完成后要将parent变黑,grandfather变红;双旋完成后要将cur变黑,grandfather变红。操作结束后,该部分的根成为黑色,不会出现连续红色节点,无需再向上调整。 


注意:平衡调整完成之后,整棵树的根节点可能变为红色。所以平衡调整结束后,一定要将根节点设置为黑色

红黑树插入的总体过程

总代码 

红黑树插入代码实现:

//插入
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 (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);
	cur->_col = RED;//插入红色节点

	if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	cur->_parent = parent;

	//parent为红,进行平衡调整
	while (parent && parent->_col == RED)
	{
		//确定grandfather
		Node* grandfather = parent->_parent;

		if (parent == grandfather->_left)
		{
			//确定uncle
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)//uncle为红,仅变色
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续向上判断
				cur = grandfather;
				parent = cur->_parent;
			}
			else//uncle为黑或不存在,旋转+变色
			{
				if (cur == parent->_left)//右单旋
				{
					RotateR(grandfather);

					//变色
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else//左右双旋
				{
					RotateL(parent);
					RotateR(grandfather);

					//变色
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;//旋转完成,直接跳出循环
			}
		}
		else
		{
			//确定uncle
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)//uncle为红,仅变色
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续向上判断
				cur = grandfather;
				parent = cur->_parent;
			}
			else//uncle为黑或不存在,旋转+变色
			{
				if (cur == parent->_right)//左单旋
				{
					RotateL(grandfather);

					//变色
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else//右左双旋
				{
					RotateR(parent);
					RotateL(grandfather);

					//变色
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;//旋转完成,直接跳出循环
			}
		}
	}

	//最后将根节点设置为黑色
	_root->_col = BLACK;
	return true;
}

//右单旋
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 (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent) ppNode->_left = subL;
		else ppNode->_right = subL;
		subL->_parent = ppNode;
	}
}

//左单旋
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 (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (ppNode->_left == parent) ppNode->_left = subR;
		else ppNode->_right = subR;
		subR->_parent = ppNode;
	}
}

4. 红黑树的查找

        红黑树的查找逻辑与传统二叉搜索树相同,注意按照查找。

代码如下:

//查找
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (key < cur->_kv.first)
		{
			cur = cur->_left;//小了往左走
		}
		else if (key > cur->_kv.first)
		{
			cur = cur->_right;//大了往右走
		}
		else
		{
			return cur;//找到了,返回
		}
	}
	return nullptr;//没找到,返回空指针
}

5. 中序遍历、拷贝构造和析构

        三个函数的实现逻辑与AVL树完全相同。注意拷贝时不要忘记parent指针。

代码如下:

//中序遍历
void Inorder()
{
	_Inorder(_root);
}
void _Inorder(Node* root)
{
	if (root == nullptr) return;
	_Inorder(root->_left);
	cout << root->_kv.first << ' ' << root->_kv.second << endl;
	_Inorder(root->_right);
}
//拷贝构造
RBTree(const RBTree<K, V>& t)
{
	_root = _Copy(t._root);
}
Node* _Copy(Node* root, Node* parent = nullptr)
{
	if (root == nullptr) return nullptr;
	Node* NewRoot = new Node(root->_kv);
	NewRoot->_col = root->_col;//复制颜色
	NewRoot->_parent = parent;//设置父指针

	//递归拷贝左子树和右子树
	NewRoot->_left = _Copy(root->_left, NewRoot);
	NewRoot->_right = _Copy(root->_right, NewRoot);
	return NewRoot;
}
//析构函数
~RBTree()
{
	_Destroy(_root);
}
void _Destroy(Node* root)
{
	if (root == nullptr) return;
	_Destroy(root->_left);
	_Destroy(root->_right);
	delete root;
}

6. 检查红黑树是否合法

        判断一棵红黑树是否合法,就要判断它是否满足红黑树的性质。可以从以下几点入手:

1. 检查根节点是否为黑色。

2. 当一个节点为红色时,判断其父亲是否是黑色。

3. 检查各个路径上的黑色节点数是否相等。

对于第三点,可以先遍历一条路径(可以选择走最左路径,避免递归),记录路径上的黑色节点个数,然后再判断其他所有路径上的黑色节点个数是否与之一致。

代码实现:

//判断是否为合法红黑树
bool IsValidRBTree()
{
	//空树,合法
	if (_root == nullptr) return true;

	//根节点为红,非法
	if (_root->_col == RED)
	{
		cout << "根节点为红" << endl;
		return false;
	}

	int refNum = 0;//记录黑色节点个数
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK) refNum++;
		cur = cur->_left;
	}

	//递归检查所有路径
	return _Check(_root, 0, refNum);
}

//路径检查
bool _Check(Node* root, int num, const int refNum)
{
	if (root == nullptr)
	{
		//遍历到空,进行黑色节点比较
		if (num != refNum)
		{
			cout << "黑色节点数量不相等" << endl;
			return false;
		}
		return true;
	}

	//检查是否有连续红色节点
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << "有连续红色节点" << endl;
		return false;
	}

	//记录当前路径的黑色节点数
	if (root->_col == BLACK) num++;

	//递归检查左子树和右子树
	return _Check(root->_left, num, refNum) && _Check(root->_right, num, refNum);
}

接下来我们写一段代码,插入一组数据,验证红黑树的合法性:

int main()
{
	RBTree<int, int> t;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto& e : a)
	{
		t.Insert({ e,e });
	}

	t.Inorder();
	cout << t.IsValidRBTree() << endl;
	return 0;
}

运行结果:

7. 程序全部代码

红黑树实现全部代码如下:

#include <iostream>
#include <utility>
using namespace std;

enum Color//枚举值表示颜色
{
	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;//指向父亲的指针
	Color _col;//颜色

	RBTreeNode(const pair<K, V>& kv)//节点构造
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _col(RED)
	{}
};

//红黑树类
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	//强制生成无参构造
	RBTree() = default;

	//拷贝构造
	RBTree(const RBTree<K, V>& t)
	{
		_root = _Copy(t._root);
	}

	//析构函数
	~RBTree()
	{
		_Destroy(_root);
	}

	//插入
	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 (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);

		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//parent为红,进行平衡调整
		while (parent && parent->_col == RED)
		{
			//确定grandfather
			Node* grandfather = parent->_parent;

			if (parent == grandfather->_left)
			{
				//确定uncle
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == RED)//uncle为红,仅变色
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上判断
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle为黑或不存在,旋转+变色
				{
					if (cur == parent->_left)//右单旋
					{
						RotateR(grandfather);

						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//左右双旋
					{
						RotateL(parent);
						RotateR(grandfather);

						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//旋转完成,直接跳出循环
				}
			}
			else
			{
				//确定uncle
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == RED)//uncle为红,仅变色
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上判断
					cur = grandfather;
					parent = cur->_parent;
				}
				else//uncle为黑或不存在,旋转+变色
				{
					if (cur == parent->_right)//左单旋
					{
						RotateL(grandfather);

						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//右左双旋
					{
						RotateR(parent);
						RotateL(grandfather);

						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;//旋转完成,直接跳出循环
				}
			}
		}

		//最后将根节点设置为黑色
		_root->_col = BLACK;
		return true;
	}

	//查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_kv.first)
			{
				cur = cur->_left;//小了往左走
			}
			else if (key > cur->_kv.first)
			{
				cur = cur->_right;//大了往右走
			}
			else
			{
				return cur;//找到了,返回
			}
		}
		return nullptr;//没找到,返回空指针
	}

	//中序遍历
	void Inorder()
	{
		_Inorder(_root);
	}

	//判断是否为合法红黑树
	bool IsValidRBTree()
	{
		//空树,合法
		if (_root == nullptr) return true;

		//根节点为红,非法
		if (_root->_col == RED)
		{
			cout << "根节点为红" << endl;
			return false;
		}

		int refNum = 0;//记录黑色节点个数
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK) refNum++;
			cur = cur->_left;
		}

		//递归检查所有路径
		return _Check(_root, 0, refNum);
	}
private:
	//右单旋
	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 (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent) ppNode->_left = subL;
			else ppNode->_right = subL;
			subL->_parent = ppNode;
		}
	}

	//左单旋
	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 (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent) ppNode->_left = subR;
			else ppNode->_right = subR;
			subR->_parent = ppNode;
		}
	}

	//中序遍历
	void _Inorder(Node* root)
	{
		if (root == nullptr) return;
		_Inorder(root->_left);
		cout << root->_kv.first << ' ' << root->_kv.second << endl;
		_Inorder(root->_right);
	}

	//拷贝构造
	Node* _Copy(Node* root, Node* parent = nullptr)
	{
		if (root == nullptr) return nullptr;
		Node* NewRoot = new Node(root->_kv);
		NewRoot->_col = root->_col;//复制颜色
		NewRoot->_parent = parent;//设置父指针

		//递归拷贝左子树和右子树
		NewRoot->_left = _Copy(root->_left, NewRoot);
		NewRoot->_right = _Copy(root->_right, NewRoot);
		return NewRoot;
	}

	//销毁
	void _Destroy(Node* root)
	{
		if (root == nullptr) return;
		_Destroy(root->_left);
		_Destroy(root->_right);
		delete root;
	}

	//路径检查
	bool _Check(Node* root, int num, const int refNum)
	{
		if (root == nullptr)
		{
			//遍历到空,进行黑色节点比较
			if (num != refNum)
			{
				cout << "黑色节点数量不相等" << endl;
				return false;
			}
			return true;
		}

		//检查是否有连续红色节点
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "有连续红色节点" << endl;
			return false;
		}

		//记录当前路径的黑色节点数
		if (root->_col == BLACK) num++;

		//递归检查左子树和右子树
		return _Check(root->_left, num, refNum) && _Check(root->_right, num, refNum);
	}

	Node* _root = nullptr;//根节点指针
};

int main()
{
	RBTree<int, int> t;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto& e : a)
	{
		t.Insert({ e,e });
	}

	t.Inorder();
	cout << t.IsValidRBTree() << endl;
	return 0;
}

总结

        红黑树通过引入节点颜色和一系列平衡规则,在保持高效查询性能的同时,优化了插入和删除操作的复杂度。与AVL树相比,红黑树对平衡的要求较为宽松,避免了频繁的旋转调整,从而提升了动态操作的效率。尽管红黑树的结构较为复杂,但它通过颜色标记、旋转操作以及路径黑色节点数量的控制,成功实现了查找、插入和删除操作的平衡。在实际应用中,红黑树被广泛用于操作系统、数据库等领域,发挥着其重要的作用。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

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

相关文章

微服务与docker

准备工作 在课前资料中给大家提供了黑马商城项目的资料,我们需要先导入这个单体项目。不过需要注意的是,本篇及后续的微服务学习都是基于Centos7系统下的Docker部署,因此你必须做好一些准备: Centos7的环境及一个好用的SSH客户端装好Docker会使用Docker如果是学习过上面Doc…

H3C-无线WLAN配置案例(二层隧道转发)

目录 1.无线wlan产生背景:2.网络拓扑:3.网络简述:4.网络配置:4.1 网络基础配置4.2 无线wlan二层隧道转发配置4.3 无线wlan验证: 1.无线wlan产生背景: 无线WLAN&#xff08;无线局域网&#xff09;的产生背景主要源于以下几个方面的需求和技术发展&#xff1a;移动性和便捷性需…

Unreal Engine 5 C++ Advanced Action RPG 十一章笔记

第十一章 In Game Widgets 本章节就是做UI2-Template Button Widget 这章节创建不同的UI 结束UI胜利UI暂停菜单主菜单加载UI新建一个按钮小组件作为模版 3-Pause Menu Template Button 继续做更多模版UI 4-Lose Screen(游戏失败UI) 做失败的UI 之前按钮模版的调度程序就在这起…

C语言初阶牛客网刷题——HJ100 等差数列【难度:简单】-20250123

1. 题目描述——HJ100 等差数列 牛客网OJ题链接—HJ100等差数列 等差数列 2&#xff0c;5&#xff0c;8&#xff0c;11&#xff0c;14&#xff0c; … 。&#xff08;从 2 开始的 3 为公差的等差数列&#xff09;&#xff0c;求等差数列前 n 项和。 注意&#xff1a;本题有多…

订单状态定时处理、来单提醒和客户催单(day10)

Spring Task 介绍 Spring Task 是 Spring 框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑。 定位&#xff1a; 定时任务框架 作用&#xff1a; 定时自动执行某段Java代码 为什么要在 Java 程序中使用 Spring Task&#xff1f; 应用场景&#xff1…

ES6 简单练习笔记--变量申明

一、ES5 变量定义 1.在全局作用域中 this 其实就是window对象 <script>console.log(window this) </script>输出结果: true 2.在全局作用域中用var定义一个变量其实就相当于在window上定义了一个属性 例如: var name "孙悟空" 其实就相当于执行了 win…

麒麟操作系统服务架构保姆级教程(十四)iptables防火墙四表五链和防火墙应用案例

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 防火墙在运维工作中有着不可或缺的重要性。首先&#xff0c;它是保障网络安全的关键防线&#xff0c;通过设置访问控制规则&#xff0c;可精准过滤非法网络流量&#xff0c;有效阻挡外部黑客攻击、恶…

【AI编辑器】字节跳动推出AI IDE——Trae,专为中文开发者深度定制

目录 一、背景 二、核心特性 2.1 AI驱动的代码自动生成 2.2 智能问答与代码补全 2.3 多语言支持 2.4 插件与扩展 三、架构 四、下载使用 4.1 下载与安装 4.2 界面与配置 五、应用实践 5.1 快速生成代码 5.2 智能问答与调试 5.3 团队协作与代码审查 六、与Cursor…

机器学习2 (笔记)(朴素贝叶斯,集成学习,KNN和matlab运用)

朴素贝叶斯模型 贝叶斯定理&#xff1a; 常见类型 算法流程 优缺点 集成学习算法 基本原理 常见方法 KNN&#xff08;聚类模型&#xff09; 算法性质&#xff1a; 核心原理&#xff1a; 算法流程 优缺点 matlab中的运用 朴素贝叶斯模型 朴素贝叶斯模型是基于贝叶斯…

【赵渝强老师】K8s中Pod探针的HTTPGetAction

在K8s集群中&#xff0c;当Pod处于运行状态时&#xff0c;kubelet通过使用探针&#xff08;Probe&#xff09;对容器的健康状态执行检查和诊断。K8s支持三种不同类型的探针&#xff0c;分别是&#xff1a;livenessProbe&#xff08;存活探针&#xff09;、readinessProbe&#…

每日十题八股-2025年1月23日

1.快排为什么时间复杂度最差是O&#xff08;n^2&#xff09; 2.快排这么强&#xff0c;那冒泡排序还有必要吗&#xff1f; 3.如果要对一个很大的数据集&#xff0c;进行排序&#xff0c;而没办法一次性在内存排序&#xff0c;这时候怎么办&#xff1f; 4.面试官&#xff1a;你的…

bat批处理删除此电脑左侧及另存为下文档视屏等多余项

发现的一个解决强迫症人士痛点的小方法&#xff0c;自整理为一键批处理&#xff0c;需者自取 他可以一键去掉电脑左侧及另存为下文档视屏等多余项&#xff0c;还界面一个清爽整洁 干干净净&#xff0c;舒服 分享的是bat文件&#xff0c;可以右键编辑查看源代码&#xff0c;绝…

计算机毕业设计hadoop+spark+hive图书推荐系统 豆瓣图书数据分析可视化大屏 豆瓣图书爬虫 知识图谱 图书大数据 大数据毕业设计 机器学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

ThinkPhp伪静态设置后,访问静态资源也提示找不到Controller

ThinkPhp没有配置伪静态时&#xff0c;除了默认的IndexController能访问&#xff0c;其他路由Controller都访问不到&#xff0c;提示404错误。配置了伪静态后就解决了这个问题。 但是当我的ThinkPhp后台项目中有静态资源放在public目录&#xff08;或子目录&#xff09;中需要…

HarmonyOS:通过(SQLite)关系型数据库实现数据持久化

一、场景介绍 关系型数据库基于SQLite组件&#xff0c;适用于存储包含复杂关系数据的场景&#xff0c;比如一个班级的学生信息&#xff0c;需要包括姓名、学号、各科成绩等&#xff0c;又或者公司的雇员信息&#xff0c;需要包括姓名、工号、职位等&#xff0c;由于数据之间有较…

豆包MarsCode 蛇年编程大作战 | 高效开发“蛇年运势预测系统”

&#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 豆包MarsCode 蛇年编程大作战 | &#x1f40d; 蛇年运势预测 在线体验地址&#xff1a;蛇年…

JAVA-快速排序

一、快速排序基本思想 快速排序是 Hoare 于 1962 年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a; 任取待排序元素序列中的某元 素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#…

acm培训 part 1(题解)

第一部分的培训为语法糖&#xff0c;stl容器以及复杂度。 题目分析 1.1 Long Loong AtCoder - abc336_a ​​ 这道题的重点在于多个o的输出。在保证前面‘L’ 的输出和后面‘ng’的输出下&#xff0c;输入需要输出的o的数字&#xff0c;来实现需要输出的效果。 代码如下 …

vulfocus/fastjson-cnvd_2017_02833复现

漏洞概述 Fastjson 是阿里巴巴开发的一个高性能的 Java 库&#xff0c;用于将 Java 对象转换成 JSON 格式&#xff08;序列化&#xff09;&#xff0c;以及将 JSON 字符串转换回 Java 对象&#xff08;反序列化&#xff09;。 fastjson在解析json的过程中,支持使用type字段来指…

Ceisum无人机巡检直播视频投射

接上次的视频投影&#xff0c;Leader告诉我这个视频投影要用在两个地方&#xff0c;一个是我原先写的轨迹回放那里&#xff0c;另一个在无人机起飞后的地图回显&#xff0c;要实时播放无人机拍摄的视频&#xff0c;还要能转镜头&#xff0c;让我把这个也接一下。 我的天&#x…