C++: 红黑树(旋转+变色)

(一)红黑树的概念

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

C++: 红黑树(旋转+变色)_第1张图片

红黑树性质规则 :

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

        2. 根节点是黑色的 

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

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

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

(二) 红黑树实现

(1)红黑树节点的定义

// 枚举值表示颜色
enum Colour
{
	RED,
	BLACK
};

template
struct RBTreeNode
{
	pair _kv;
	RBTreeNode* _left;
	RBTreeNode* _right;
	RBTreeNode* _parent;
	Colour _col;


	RBTreeNode(const pair& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_col(RED)
	{}
};

(2)红黑树结构

实际上为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了 与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft 域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点

但是这里为了方便实现(也更方便理解红黑树的规则),根结点的父亲结点是指向空的

template
class RBTree
{
	typedef RBTreeNode Node;
public:
	bool Insert(const pair& kv);

private:

	// 左单旋
	void RotateL(Node* parent);


	// 右单旋
	void RotateR(Node* parent);


	Node* _root = nullptr;
};

(3)红黑树的插入

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

        1. 按照二叉搜索的树规则插入新节点

        2. 检测新节点插入后,红黑树的性质是否造到破坏(着重理解)

 

检验红黑树性质是否破坏

        1.因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何 性质,则不需要调整

        2.当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:

        情况一: cur为红,parent为红,grandparent为黑,uncle存在且为红(进行变色处理)

C++: 红黑树(旋转+变色)_第2张图片

        解决方式:

                将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

需要注意的是:

      要判断parent是grand的左孩子还是右孩子。

        情况二: cur为红,p为红,g为黑,u不存在或者u存在且为黑(旋转+变色)

C++: 红黑树(旋转+变色)_第3张图片

 

 解决方式:

1.parent为grand的左孩子时:

        若cur为parent的左孩子,则进行对grand右单旋转;

        若cur为parent的右孩子,则针对parent做左单旋转,再对grand右单旋转;

2.parent为grand的右孩子时

        若cur为parent的右孩子,则进行grand左单旋转;

        若cur为parent的左孩子,则针对parent做右单旋转,再对grand左单旋转;

插入实现代码如下:

 (旋转的理解可以看我AVL树的博客)

bool Insert(const pair& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;

	//找到待插入位置
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);
	//将插入结点的值进行连接
	if (parent->_kv.first > kv.first)
	{
		parent->_left = cur;
	}
	else if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}

	cur->_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不存在 或者 uncle存在且为黑,旋转+变色
			{
				if (cur == parent->_left)
				{
					//旋转
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;

				}
				else    // cur == parent->_right
				{
					RotateL(parent);
					RotateR(grandfather);

					cur->_col = BLACK;
					grandfather->_col = RED;
				}

				break; //旋转+变色 之后 已经处理完成
			}
		}
		else   //parent == grandfather->_right
		{
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = cur->_parent;
			}
			else    //uncle不存在 或者为黑
			{
				if (cur == parent->_left)
				{
					RotateR(parent);
					RotateL(grandfather);

					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					RotateL(grandfather);

					parent->_col = BLACK;
					grandfather->_col = RED;
				}

				break; //旋转+变色后已经完成插入,即可退出
			}
		}
	}

	_root->_col = BLACK;  //根的结点都是黑色的,这一步很重要
	return true;
}

// 左单旋
void RotateL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	parent->_right = curleft;
	if (curleft)
	{
		curleft->_parent = parent;
	}


	Node* Pparent = parent->_parent;
	cur->_left = parent;
	parent->_parent = cur;

	if (Pparent == nullptr)
	{
		cur->_parent = nullptr;
	}
	else //父亲的父亲不是空
	{
		if (Pparent->_kv.first < cur->_kv.first)
		{
			Pparent->_right = cur;
		}
		else
		{
			Pparent->_left = cur;
		}

		cur->_parent = Pparent;
	}

}

// 右单旋
void RotateR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;

	parent->_left = curright;
	if (curright)
	{
		curright->_parent = parent;
	}

	Node* Pparent = parent->_parent;
	cur->_right = parent;
	parent->_parent = cur;

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

		cur->_parent = Pparent;
	}
}

 (三)红黑树的验证

红黑树的检测分为两步:

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

        2. 检测其是否满足红黑树的性质(代码展示)

bool IsBalanceTree()
{
	if (_root == nullptr)
		return true;


	if (_root->_col == RED)
		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 blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历走到空时,意味着一条路径走完了
		//cout << blackNum << endl;
		if (refNum != blackNum)
		{
			cout << "存在黑色结点的数量不相等的路径" << endl;
			return false;
		}
		return true;
	}


	// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
	if (root->_col == RED && root->_parent && root->_parent->_col == RED)
	{
		cout << root->_kv.first << "存在连续的红色结点" << endl;
		return false;
	}


	if (root->_col == BLACK)
	{
		blackNum++;
	}


	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}

红黑树总结

一、定义与核心特性

红黑树是一种自平衡的二叉搜索树,通过颜色标记(红/黑)和旋转操作维护近似平衡,确保查找、插入、删除操作的时间复杂度为O(log⁡n)。其核心特性由以下五点构成:

  1. 根节点为黑色
  2. 叶子节点(NIL节点)为黑色
  3. 红色节点的子节点必须为黑色(即父子节点不能同时为红)
  4. 从任一节点到其所有叶子节点的路径中,黑色节点数量相同
  5. 新插入的节点默认为红色,后续通过调整满足规则。
二、运行规则:插入与删除调整

当插入或删除节点破坏红黑树特性时,通过以下操作调整:

  1. 颜色变换:例如将父节点和叔节点由红变黑,祖父节点由黑变红;
  2. 旋转操作
    • 左旋:以某节点为支点,右子节点上升为父节点;
    • 右旋:以某节点为支点,左子节点上升为父节点;
  3. 组合调整:如插入时可能需多次旋转+变色(如“红-黑-红”结构调整为全黑)。
三、与AVL树的对比
  • 平衡标准:AVL树要求左右子树高度差≤1,而红黑树允许一定程度的不平衡,但通过特性4限制树高;
  • 调整频率:AVL树插入/删除后需频繁调整,红黑树调整次数更少,综合性能更优;
  • 适用场景:红黑树适合频繁修改的数据集(如内存数据库),AVL树适合读多写少的场景。
  • 红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。
四、应用场景

红黑树广泛应用于需要高效动态数据管理的场景:

  • 编程语言库:如Java的TreeMap、C++ STL的mapset
  • 文件系统:如Ext3/Ext4的目录索引;
  • 实时计算:保证操作时间稳定性的任务调度。

你可能感兴趣的:(c++,红黑树)