C++:红黑树

一、红黑树的概念

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

总的来说,二叉搜索树(BST)可能会退化成单支树,而AVL树旋转过于严格,所以RB树就是一种折中的解决方法。

二、红黑树的性质

1. 每个结点只有红色和黑色两种情况
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

满足以上要求就能让让红黑树确保没有一条路径会比其他路径长出俩倍的。(极端情况下,最短路径全为黑色结点,最长路径黑色结点和红色结点相互交错,此时最长路径等于最短路径的两倍)

三、红黑树结点的定义

enum colour
{
	RED,BLACK
};
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _parent;
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	colour _col;
	T _data;

	RBTreeNode(const T& data = T() )
		:_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
		,_col(RED)
		,_data(data)
	{}
};

分析构建的新结点究竟设置成红色还是黑色好一点,取决于以上的规则三和规则四。

       首先我们要知道,插入这个节点之前该树一定是一棵正确的红黑树,必然满足上面的两个规则,如果我们新插入的节点默认选择黑色的话,那么凭空多出来的黑色节点必然会导致规则2被破坏,也就是说我们每插入一次就要去调整。但如果选择的是红色的话,如果红色结点的父亲节点是黑色,那么就不需要进行调整,如果父亲节点是红色才要进行调整。所以黑色是必然会破坏规则,但是红色不一定,所以默认选择设置成红色。

四、红黑树的插入和调整

情况1:如果待插入前是空树, 那么新插入元素自动成为根,并且将其设置为黑色.

情况2: 因为我们把默认节点设置为红色,所以如果被插入位置的父亲节点是黑色的话,就不需要进行调整。

情况3:u(叔叔)存在且为红色结点

 此时无论cur是p的左孩子还是右孩子都不影响。因为g的父亲结点可能是红色结点,故继续向上调整。

情况4:u存在且为黑或u不存在

4.1 当p为g左孩子时:

4.1.1、cur为p的左孩子

对g进行右单旋,使p变成根节点,cur和g的颜色改为BLACK,p的颜色改为RED,调整结束。

4.1.2、cur为p的右孩子

对p进行左单旋,然后对g进行右单旋,最终将g的颜色改为RED,cur的颜色改为BLACK,结束调整.

4.2当p为g的右孩子时与p为g的左孩子互为镜像类比即可。

五、红黑树插入旋转代码实现

	bool Insert(const T& data)//为了方便后续封装,增加一个_head为头节点
	{
		
		if (_head->_parent == _head)
		{
            Node* newnode = new Node(data);
			_head->_parent = newnode;
			newnode->_parent = _head;
			_head->_left = newnode;
			_head->_right = newnode;
			newnode->_col = BLACK;
			return true;
		}
		Node* cur = _head->_parent;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_data < data)
				cur=cur->_right;
			else if (cur->_data > data)
				cur=cur->_left;
			else
				return false;
		}
		Node* newnode = new Node(data);
		newnode->_parent = parent;//插入节点
		if (parent->_data > data)
			parent->_left = newnode;
		else
			parent->_right = newnode;
		cur = newnode;//向上调整
		while (parent!=_head && parent->_parent!=_head && parent->_col == RED)
		{
			Node* pparent = parent->_parent;
			if (parent == pparent->_left)
			{
				Node* uncle = pparent->_right;
				if (uncle && uncle->_col == RED)
				{
					//存在且为红;
					uncle->_col = parent->_col = BLACK;
					pparent->_col = RED;
					cur = pparent;
					parent = cur->_parent;
				}
				else
				{
					//不存在或者为黑;
					if (cur == parent->_left)
					{
						RotateR(pparent);
						pparent->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateL(parent);
						RotateR(pparent);
						cur->_col = BLACK;
						pparent->_col = RED;
					}
					break;
				}
			}
			else
			{
				Node* uncle = pparent->_left;
				if (uncle && uncle->_col == RED)
				{
					//存在且为红;
					uncle->_col = parent->_col = BLACK;
					pparent->_col = RED;
					cur = pparent;
					parent = cur->_parent;
				}
				else
				{
					//不存在或者为黑;
					if (cur == parent->_right)
					{
						RotateL(pparent);
						pparent->_col = RED;
						parent->_col = BLACK;
					}
					else
					{
						RotateR(parent);
						RotateL(pparent);
						cur->_col = BLACK;
						pparent->_col = RED;
					}
					break;
				}
			}

		}
		_head->_parent->_col = BLACK;//根节点置为BLACK
		_head->_left = LeftMost();   //头节点左指向最小节点
		_head->_right = RightMost(); //右指向最大节点
		return true;
	}


// 左单旋
void RotateL(Node* parent)
{    
	Node* cur = parent->_right;
	cur->_parent = parent->_parent;
	parent->_right = cur->_left;
	cur->_left = parent;
	if (parent->_parent == _head)
		_head->_parent = cur;
	else
	{
		if (parent->_parent->_left == parent)
			parent->_parent->_left = cur;
		else
			parent->_parent->_right = cur;
	}
	parent->_parent = cur;
	
}
// 右单旋
void RotateR(Node* parent)
{   
	Node* cur = parent->_left;
	cur->_parent = parent->_parent;
	parent->_left = cur->_right;
	cur->_right = parent;
	if (parent->_parent == _head)
		_head->_parent = cur;
	else
	{
		if (parent->_parent->_left == parent)
			parent->_parent->_left = cur;
		else
			parent->_parent->_right = cur;
	}
	parent->_parent = cur;

}

五、红黑树的验证

// 检测红黑树是否为有效的红黑树
bool IsValidRBTRee()
{
	Node* root = GetRoot();
	if (root == nullptr)
		return true;
	if (root->_col != BLACK)
		return false;
	int count = 0;
	Node* obj = root;
	while (obj)//记录一条路径上的黑色节点数量
	{
		count += obj->_col == BLACK ? 1 : 0;
		obj = obj->_left;
	}
	return _IsValidRBTree(root, count, 0);
}


private:
bool _IsValidRBTree(Node* pRoot, size_t blackCount, size_t pathBlack)
{
	if (pRoot == nullptr)
	{
		if (blackCount == pathBlack)
			return true;
		return false;
	}
	if (pRoot->_col == RED && pRoot->_parent != _head && pRoot->_parent->_col == RED)
		return false;//不能有两个连续红色节点
	int k = pRoot->_col == BLACK ? 1 : 0;
		return _IsValidRBTree(pRoot->_left, blackCount, pathBlack + k)&& 
               _IsValidRBTree(pRoot->_right, blackCount, pathBlack + k);
}

六、红黑树和AVL树的比较

  1. 平衡策略

    • AVL树:通过在每个节点上存储一个平衡因子(左子树高度减右子树高度),并在插入或删除时进行旋转来维持平衡。其最大平衡因子的绝对值为1。
    • 红黑树:通过颜色属性来维护平衡,规定了必要的旋转操作以保证树的平衡。
  2. 性能对比

    • 在平均情况下,红黑树的查找、插入和删除操作的时间复杂度均为O(log n)。
    • AVL树的性能略微优于红黑树,因为它的平衡因子更加严格,通常情况下AVL树的高度更低。
  3. 使用场景

    • AVL树:适用于对性能要求较高,且对极端情况有准备的场景。
    • 红黑树:更常用于插入和删除操作频繁的场景,因为其平衡操作相对简单,效率更高。
  4. 复杂度分析

    AVL树和红黑树:即使在极端情况下,性能都能保持在O(log n)。

( 1 )AVL更平衡,结构上更加直观,时间效能针对读取而言更高(搜索效率高);维护稍慢,空间开销较大。
( 2 ) 红黑树,读取略逊于AVL,维护强于AVL(复衡效率高),空间开销与AVL类似,内容极多时略优于AVL,维护优于AVL。

(3)总体来说RB的整体性能高于AVL,因此在实际应用中基本上都是用的RB。

  • 16
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值