【数据结构】深入理解红黑树:平衡性与高效性的完美结合

什么是红黑树?

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

1. 红黑树的性质

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

2. 红黑树节点的定义

enum Colour
{
   
   
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
   
   
public:
	pair<K, V> _kv;
	Colour _col;
	RBTreeNode<K, V>* _pLeft;
	RBTreeNode<K, V>* _pRight;
	RBTreeNode<K, V>* _pParent;
	
	RBTreeNode(const pair<K, V>& kv) // 构造新增节点
		:_pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _kv(kv)
		, _col(RED) // 默认为红色
	{
   
   }
};

在节点的定义中,为什么要将节点的默认颜色给成红色的?

红黑树是一种自平衡的二叉搜索树,其节点上会附加一个颜色属性,可以是红色或黑色。在红黑树中将节点默认颜色设置为红色有助于满足红黑树的性质,确保树的平衡性和高效性。

具体原因如下:

  1. 约束节点插入位置:当新节点插入红黑树时,将其默认设为红色可以帮助维持红黑树的平衡性,因为红色节点的存在不会破坏红黑树的性质,而黑色节点的插入可能会导致需要进行额外的旋转操作来调整平衡。
  2. 简化旋转操作:默认将节点设置为红色可以减少旋转操作的次数,因为插入红色节点后,可以通过变换颜色和旋转来达到平衡,而插入黑色节点则可能需要更多的旋转操作。
  3. 保证黑色平衡:红黑树要求任意一条路径上的黑色节点数量相等,因此默认将节点设置为红色可以更容易地保证这一性质,避免破坏黑色平衡。

总的来说,将节点默认设置为红色是为了简化插入和维护红黑树的操作,同时确保树的平衡性和高效性。

3. 红黑树结构

为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点,如下:
在这里插入图片描述

4. 红黑树的插入操作

在代码实现中,红黑树的插入操作主要包括以下步骤:

  • 首先按照二叉查找树的规则将新节点插入到适当的位置,并将其颜色设为红色。
  • 根据红黑树的性质,通过旋转和变色操作来维持平衡性。
  • 在插入过程中,根据父节点、祖父节点和叔叔节点的颜色情况,进行不同的–旋转和变色操作,确保红黑树的性质得以保持。
// 在RB树中插入值为kv的节点
bool Insert(const pair<K, V>& kv) {
   
   
	// 插入节点
	if (_pRoot == nullptr) {
   
   
		_pRoot = new Node(kv);
		_pRoot->_col = BLACK;
		return true;
	}

	Node* pParent = nullptr;
	Node* pCur = _pRoot;
	while (pCur) {
   
   
		pParent = pCur;
		if (kv.first < pCur->_kv.first)
			pCur = pCur->_pLeft; // 往左子树查找
		else if (kv.first > pCur->_kv.first)
			pCur = pCur->_pRight; // 往右子树查找
		else
			return false; // 重复值不插入
	}

	// 创建新节点
	pCur = new Node(kv); // 红色的
	if (kv.first < pParent->_kv.first)
		pParent->_pLeft = pCur;
	else
		pParent->_pRight = pCur;

	pCur->_pParent = pParent;

	// 插入节点后,更新
	while (pParent && pParent->_col == RED) {
   
   
		Node* pGrandfather = pParent->_pParent;
		if (pGrandfather == nullptr)
			break; // 处理根节点
		// 找叔叔
		if (pParent == pGrandfather->_pLeft) {
   
   
			Node* pUncle = pGrandfather->_pRight;
			// case1: 叔叔存在且为RED
			if (pUncle && pUncle->_col == RED) {
   
   
				// 变色
				pParent->_col = pUncle->_col = BLACK;
				pGrandfather->_col = RED;
				// 继续往上处理
				pCur = pGrandfather;
				pParent = pCur->_pParent;
			}
			else {
   
   
				// case2: 叔叔不存在或者存在且为黑 (旋转+变色)
				if (pCur == pParent->
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Q_hd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值