【数据结构】红黑树

目录

一、红黑树的概念

二、红黑树节点的定义

三、红黑树的插入

插入之后的平衡调整

cucle不存在:

单旋+变色

双旋+变色

cucle存在且为黑:

四、红黑树的查询

五、红黑树的验证

六、红黑树和AVL树对比


一、红黑树的概念

红黑树是⼀棵自平衡 ⼆叉搜索树,他的每个结点增加⼀个 存储位 来表⽰ 结点的颜色 ,可以是红色或者黑色。
与 AVL 树追求"严格平衡"不同,红黑树 允许一定程度的不平衡 。AVL 树通过"平衡因子"维持平衡,而红黑树通过给节点 着色(红色或黑色)来确保高效性。
在保证增删改查效率没有太大影响的前提下,显著减少了平衡调整(旋转)的次数,从而提升总体效率。
红黑树的性质如下:

  1. 节点颜色 :每个节点要么是红色 ,要么是黑色
  2. 根节点 :根节点必须是黑色
  3. 叶子节点 :所有叶子节点(即 NULL 节点/空指针)均为黑色
  4. 红色约束 :如果一个节点是红色,则其子节点必须是黑色。路径上不能有连续的红色节点。**
  5. 黑色高度 :从根节点到任意 NULL 节点的所有路径上,黑色节点的数量都相同

红黑树最长路径不超过最短路径的两倍

二、红黑树节点的定义

红黑树节点定义如下,采用三叉链结构,并添加一个变量表示节点颜色。

cpp 复制代码
enum Colour
{
	RED,
	BLACK
};


template<class K, class V>
struct RBTreeNode
{
	//三叉链
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;

	//存储的键值对
	pair<K, V> _kv;

	//结点的颜色
	int _col; //红/黑

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

三、红黑树的插入

红黑树插入步骤分为3步:

1.按照二叉搜索树规则,查找插入位置。

2.将待插入节点插入到树中。

3.若插入节点的父节点是红色的,则要对红黑树进行调整。

tip:新插入节点设置为红色,如果设置成黑色则会导致当前路径比其他路径多一个黑色节点,破坏红黑树特性。

插入之后的平衡调整

插入完成后,并不是会一定对红黑树调整

若插入之后的父节点是黑色,则不用对红黑树调整。如果插入前是空树,则新插入元素变为根,并将其设置为黑色。

若插入之后的父节点是红色,则要对红黑树进行调整,因为父节点是红色的,所以父节点不是根节点(根节点是黑色的),一定有组父节点。

场景1:只变色

当parent和uncle都是红色,grandfather是黑色时,新插入节点会导致出现两个连续红色节点,触发变色,parent和uncle变成黑色,grandfather变成红色,这样就满足了红黑树的性质。

grandfather变红后可能还会再次触发两个连续红节点,再次进行递归向上变色,直到根节点或者无连续的红节点停止。

场景2:旋转+变色

要调整的情况,插入时,parent一定为红,grandfather一定为黑,唯一变量是uncle。

uncle还可能是黑色或空节点。

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

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

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

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

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

cucle不存在:

这种情况下,路径黑色节点个数不一样,只进行变色不能满足红黑树的性质,这时需要配合旋转来解决问题。

单旋+变色

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

双旋+变色

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

cucle存在且为黑:

uncle 为存在且为黑时,cur 一定是向上调整时标记的节点。

a,b,c,d,e表示路径黑节点个数,第一步到第二部是发生了变色,这时叔叔是黑色,就需要先旋转再变色了,**单旋完成后要将 parent 变黑,grandfather 变红;双旋完成后要将 cur 变黑,grandfather 变红。**操作结束后,该部分的根成为黑色,停止向上递归。

实现代码:

cpp 复制代码
//插入
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)
	{
		
		Node* grandfather = parent->_parent;
 
		if (parent == grandfather->_left)
		{
			
			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;
	}
}

四、红黑树的查询

红黑树的查询的实现和普通二叉搜索树一样

cpp 复制代码
//查找
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;
}

五、红黑树的验证

红黑树是特殊的二叉搜索树,可以用中序遍历来判断是否符合二叉树的性质。

cpp 复制代码
bool IsBalance()
{
	if (_root && _root->_col == RED)
	{
		cout << "根节点颜色是红色" << endl;
		return false;
	}
	int benchmark = 0;//找到一条路径作为基准值 然后看看其他路径是否相等
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			++benchmark;
		cur = cur->_left;
	}
 
	// 连续红色节点
	return _Check(_root, 0, benchmark);
}
bool _Check(Node* root, int blackNum, int benchmark)
{
	if (root == nullptr)
	{
		if (benchmark != blackNum)
		{
			cout << "某条路径黑色节点的数量不相等" << endl;
			return false;
		}
 
		return true;
	}
 
	if (root->_col == BLACK)
	{
		++blackNum;
	}
 
	if (root->_col == RED
		&& root->_parent
		&& root->_parent->_col == RED)
	{
		cout << "存在连续的红色节点" << endl;
		return false;
	}
 
	return _Check(root->_left, blackNum, benchmark)
		&& _Check(root->_right, blackNum, benchmark);
}

六、红黑树和AVL树对比

红黑树 (RB-Tree) 与 AVL 树同为增删查改时间复杂度达 O(logN) 的自平衡二叉查找树

  1. AVL 树通过严格控制左右子树高度差绝对值不超过 1 来维持极度扁平的结构,具备最优的查询效率,但在执行删除等修改操作时需回溯维护整条路径的平衡,引发高达 O(logN) 量级的旋转,仅适用于读多写少的静态场景;
  2. 红黑树则通过节点颜色约束实现"最长可能路径不超过最短可能路径 2 倍"的近似平衡,以略微牺牲查询性能为代价,将插入与删除引发的旋转次数严格收敛于 O(1) 量级(分别最多 2 次和 3 次)。
  3. 总体来说RB的整体性能高于AVL,因此在实际应用中基本上都是用的RB
相关推荐
AI周红伟1 小时前
周红伟:OpenAI 首席运营官,尚未真正看到人工智能渗透到企业业务流程中
人工智能·算法·性能优化
Once_day1 小时前
GCC编译(7)链接脚本LinkerScripts
c语言·c++·编译和链接·程序员自我修养
问好眼1 小时前
《算法竞赛进阶指南》0x01 位运算-2.增加模数
c++·算法·位运算·信息学奥赛
Full Stack Developme1 小时前
哈希是什么
算法·哈希算法
薛定e的猫咪2 小时前
【AAAI 2025】基于扩散模型的昂贵多目标贝叶斯优化
论文阅读·人工智能·算法
菜鸟小九2 小时前
redis原理篇(五种数据结构)
数据结构·数据库·redis
Fox爱分享2 小时前
拼多多面试: 设计“砍一刀”算法,怎么防止被刷破产?90% 的人死在了“最后 0.01 元”
后端·算法·面试
NGC_66112 小时前
归并排序算法
java·数据结构·算法
你撅嘴真丑2 小时前
第十章-训练参考
算法