C++红黑树的原理及实现

文章介绍了红黑树作为一种自平衡二叉搜索树的特性,包括其五个性质。红黑树节点默认为红色以避免破坏特定性质。插入操作后,通过调整保持平衡,如右旋、左旋等策略。文章还提供了红黑树插入的代码示例和平衡验证方法。
摘要由CSDN通过智能技术生成

上一节我们学习的是AVL树,AVL树是一棵几乎完美接近平衡的树,但是map和set底层的封装用的都是红黑树,可见红黑树相比较AVL树有着更突出的优势。现在让我们看一看红黑树的底层原理以及具体的代码实现吧。

 

红黑树的概念:

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

 红黑树的性质:

1.从任何一个结点开始到任何一条路径的叶子节点的黑色结点的数量是相同的。

2.整棵树中没有相邻的红色结点。一个结点是红色,那它相邻的孩子结点必定是黑色的。

3.根结点是黑色的。

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

红黑树结点的定义:

enum Colour
{
	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;
	Colour _col;

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

在构造函数中,为什么把颜色默认设置成红色?

因为红色是一种安全的做法,如果插入的结点是黑色的,那么它必定违反性质1。而插入红色结点不一样,如果插入节点的父节点是黑色的,那么插入就不会违反上面性质。所以这是一个概率问题,很明显插入黑色结点出现问题的概率比插入红色结点要高得多。

红黑树的插入:

红黑树的插入和AVL树的插入条件一样,它保持了原来AVL树的特性:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = Black;
			return make_pair(iterator(_root), true);  //make_pair方式返回
		}

		Node* parent = nullptr;
		Node* cur = _root;
		KeyofT kot;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) >kot( data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}
		
		cur = new Node(data);
		Node* newnode = cur;

		cur->_col = Red;
		
		if (kot(parent->_data) > kot(cur->_data))
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
private:
     Node* _root=nullptr;
}

数据插进去以后,因为我们插入的是红色结点,所以要进行二叉树的平衡。如果该红色结点的双亲是黑色结点,那么不违反条件不需要进行平衡。如果双亲结点为红色结点,那么就违反了有连续红节点的规则,所以要对它进行平衡处理:

情况一:cur为红,p为红,u为红,g为黑。

 如果g为根节点,调整完成后g本应为红,但是根节点的性质是根节点为黑色,所以将g变成黑。

 如果g不为根节点,此时g的结点颜色为红,当g的双亲也为红色时就需要继续向上调整。

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

情况二 : cur 为红, p 为红, g 为黑, u 不存在 /u 存在且为黑

 情况二我们需要分情况讨论:u存在或者u不存在。但是u存在那一定是黑色结点。

处理方法:以p为轴心进行一次右单旋,右单旋过后p结点变成黑色,g结点变成红色。平衡以后的二叉树图如下:

 相反如果p在右u在左,cur在p的右侧,则以p为轴心进行一次左单旋。

情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑

 情况三我们发现上图的g、p、cur成一个折线形,且p是凸出来的,所以先以p为轴心进行一次左单旋,再以g为中心进行一次右单旋。平衡代码如下:

//调整平衡,并且更新颜色
		while (parent && parent->_col == Red)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				if (uncle && uncle->_col == Red)
				{
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_left == cur)
					{
						RotateR(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else if (parent->_right == cur)
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						assert(false);
					}
					break;
		        }
			}
			else
			{
				Node* uncle = grandfather->_left;
				if (uncle && uncle->_col == Red)
				{
					parent->_col = uncle->_col = Black;
					grandfather->_col = Red;
					cur = grandfather;
					parent = cur->_parent;
				}
				else
				{
					if (parent->_right == cur)
					{
						RotateL(grandfather);
						parent->_col = Black;
						grandfather->_col = Red;
					}
					else if (parent->_left == cur)
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = Black;
						grandfather->_col = Red;
					}
					else
					{
						assert(false);
					}
					break;
				}

			}
		} 
		_root->_col = Black;
		return make_pair(iterator(newnode), true);
	}

右左单旋的代码在AVL章节中就有,这里我们就不在这里详细展示了。

红黑树实现的完整代码其实并非很重要,我们只需要知道红黑树的性质以及它进行调节平衡的各种情况,接下来我们来谈一谈红黑树平衡的验证方法:

bool IsRBTree()
	{
		//空树也符合红黑树
		if (_root == nullptr)
		{
			return true;
		 }

		
		//验证根节点是否为黑色
		if (_root->_col == Red)
		{
			cout << "根节点为红色,不符合规则" << endl;
			return false; 
		}


		int ref = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == Black)
			{
				++ref;
			}
			cur = cur->_left;
		}

		return _IsRBTree(_root, 0, ref);
		
		
	}

	bool _IsRBTree(Node* root, int count, int ref)
	{
		if (root == nullptr)
		{
			if (count != ref)
			{
				cout << "每条路径的黑色节点数不相等" << endl;
				return false;
			}
			return true;
		}

		if (root->_col == Black)
		{
			++count;
		}

		Node* ppnode = root->_parent;
		if (ppnode && root->_col == Red && ppnode->_col == Red)
		{
			cout << "存在相连的红色节点" << endl;
			return false;
		}

		return _IsRBTree(root->_left, count, ref) && _IsRBTree(root->_right, count, ref);
	}

红黑树的讲解就到这里,感谢大家的阅读以及支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值