Bootstrap

C++红黑树插入操作的模拟实现

1.红黑树概念

1.1什么是红黑树

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。mapset的底层就是以红黑树为框架,因为红黑树与AVL相比在插入和删除时更有优势。

如下图即为一棵红黑树,通过继续对红黑树性质的学习,我们将红黑树如何实现接近平衡

1.2.红黑树的性质

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

2. 节点是黑色的 

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的 (即不能出现连续的红色节点

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

为什么满足上面的性质,红黑树就能确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的?

由图推广,最长路径一定是全黑设最短路径长度为h,最长路径一定为一黑一红交错排列。

则:最短路径 * 2 >= 最长路径

最长路径 <= 2h

这只是符合规则的理论最短和最长,实践中最短和最长不一定存在。

2.红黑树的结构及插入

2.1红黑树的结构

红黑树通过维护规则保持树的相对平衡,当插入节点破坏规则时,将进行变色旋转操作。在进行变色或旋转操作时,需要找到插入点的父节点。为方便操作这里将红黑树的节点设计为三叉链的结构——分别指向左孩子、右孩子、父亲节点。

节点的定义如下

//节点的颜色
enum Colour
{
	RED,
	BLACK
};


//模版参数Key和Value
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
		, _kv(kv), _colour(RED)//默认每个节点颜色为红色
	{}
	RBTreeNode<K, V>* _pLeft;// 该节点的左孩子
	RBTreeNode<K, V>* _pRight; // 该节点的右孩子
	RBTreeNode<K, V>* _pParent; // 该节点的双亲
	pair<K, V> _kv; // 该节点储存的数据  
	Colour _colour;// 该节点的颜色

};

树的定义如下

 
template<class K, class V>
class RBTree {
	typedef RBTreeNode<K, V> Node;
	Node* _root;

public:
	RBTree() = default;
	~RBTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;

		Destroy(root->_pLeft);
		Destroy(root->_pRight);
		delete root;
	}

};

2.2红黑树的插入操作

 我们先来讨论插入的节点应该定义为什么颜色

1.定义为黑色,除非为整棵树的根,否则插入一个黑色节点必定使根到这个节点路径的黑色节点数量+1,则必定违反红黑树性质4——每个结点到其所有后代叶结点的简单路径上,不包含相同数目的黑色结点。

2.定义为红色,插入一个红色节点,若其父节点是红色的则会违反红黑树性质2,出现了连续的红色节点。

然而综合两种情况,插入黑色节点时必定违反规则(除根外),插入红色节点时可能违反规则,我们优先选择风险更小的那种插入方式将插入节点默认为红色,然后在违反规则时特殊处理

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

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

2.检测新节点插入后,红黑树的性质是否遭到破坏

   因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违     反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:

下文中,我们令 cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点(g:grandfather、p:parent、u:uncle)

而插入时双亲节点为红时又存在以下两种情况,需要特殊处理

 情况一:cur为红,p为红,g为黑,u存在且为红

图中三角形节点a、b、c、d、e为抽象的树,可以为空或一棵子树,但他们的存在保证符合性质4

 这种情况有以下几种具体情况

1.a、b、c、d、e都为空

2.a、b、c、d、e不为空但每条路径黑色节点数目相等

3.cur原本为黑,a、b的插入使cur变红

这样的情况,对子树的调整方法:p、u变黑,g变红,这样的调整方法因为g变红继续向祖先调整(不确定g的双亲节点的颜色)

这种情况变色的调整方法使g的黑色向g的孩子转移,没有增加每条路径黑色节点的数目

如下

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

u存在且为黑色的情况a、b、c路径至少有一个黑色节点

 这时我们用变色处理不了问题了,必须要旋转处理

而旋转也分为两种情况

二.1.纯粹的一边高的情况,如图

调整方法: 单次旋转,p变黑,g变红

这种调整会使根到cur的孩子所有路径高度降低(使较高路径变短),且维护了规则

下图为右旋(即向右旋转),左旋思想类似

二.2.一边的中间高的情况

如cur在p的右边的情况,如图

这种情况一次左旋解决不了问题,我们可以先旋转一次变成二.1.那样一边高的情况,再旋转调整

调整方法:先在p子树进行左旋,再对g右旋,cur变黑,g变红

 下面给出左右旋及插入操作的完整代码

	//红黑树的插入操作
	void Insert(const pair<K, V>& kv)
	{
		Node* newnode = new Node(kv);
		Node* cur = _root;
		Node* parent = nullptr;

		//为空树,若为根节点,颜色为黑色
		if (_root == nullptr)
		{
			newnode->_colour = BLACK;
			_root = newnode;
			return;
		}


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

		//链接节点
		cur = newnode;
		cur->_pParent = parent;
		if (kv.first < parent->_kv.first)
		{
			parent->_pLeft = cur;
		}
		else
		{
			parent->_pRight = cur;
		}


		//根据规则进行调整
		//使用循环调整到根节点
		while (parent)
		{
			//若p为黑,不继续向上调整
			if (parent->_colour == BLACK)
			{
				break;
			}
			//若p为红,进行调整
			else
			{
				//p为红则必有parent,且grandpa必为黑,因为根节点为黑
				Node* grandpa = parent->_pParent;
				Node* uncle = nullptr;
				if (parent == grandpa->_pLeft)
				{
					uncle = grandpa->_pRight;
				}
				else
				{
					uncle = grandpa->_pLeft;
				}
				//情况1:p和u都为红色,将p和u变黑,g变红,并继续向上更新
				if (parent->_colour == RED && uncle != nullptr && uncle->_colour == RED)
				{
					parent->_colour = BLACK;
					uncle->_colour = BLACK;
					grandpa->_colour = RED;

					//若grandpa为根节点,将其变回黑色,退出循环
					if (grandpa == _root)
					{
						grandpa->_colour = BLACK;
						break;
					}

					//迭代进行下一轮循环
					cur = grandpa;
					parent = cur->_pParent;
				}
				//情况2:p为红,u为黑,进行旋转后再变色
				else if (parent->_colour == RED && (uncle == nullptr || uncle->_colour == BLACK))
				{
					//先判断p为g的左还是右
					if (parent == grandpa->_pLeft)
					{
						//2.1 单旋的情况,p为g的左孩子,cur为p的左孩子,右单旋
						if (cur == parent->_pLeft)
						{
							RotatoR(grandpa);

							//颜色调整
							parent->_colour = BLACK;
							grandpa->_colour = RED;
						}
						//2.2 双旋的情况,p为g的左孩子,cur为p的右孩子,先p左单旋,再g右单旋
						else
						{
							RotatoL(parent);
							RotatoR(grandpa);

							//颜色调整
							cur->_colour = BLACK;
							grandpa->_colour = RED;
						}
					}
					else
					{
						//2.1 单旋的情况,p为g的右孩子,cur为p的右孩子,左单旋
						if (cur == parent->_pRight)
						{
							RotatoL(grandpa);

							//颜色调整
							parent->_colour = BLACK;
							grandpa->_colour = RED;
						}
						//2.2 双旋的情况,p为g的右孩子,cur为p的左孩子,先p右单旋,再g左单旋
						else
						{
							RotatoR(parent);
							RotatoL(grandpa);

							//颜色调整
							cur->_colour = BLACK;
							grandpa->_colour = RED;
						}
					}

					//子树根节点为黑,不会影响祖先,退出循环
					break;

				}
			}
		}
	}


	//左单旋
	//可以使根到cur的孩子所有路径高度降低,且维护了规则
	void RotatoL(Node* parent)
	{
		Node* subR = parent->_pRight;
		Node* subRL = subR->_pLeft;
		Node* parentParent = parent->_pParent;

		//先链接节点
		if (subRL != nullptr)
			subRL->_pParent = parent;
		parent->_pRight = subRL;

		parent->_pParent = subR;
		subR->_pLeft = parent;

		subR->_pParent = parentParent;
		if (parentParent == nullptr)
		{
			//若parentParent为空则原Parent为整个树的根
			_root = subR;
		}
		else
		{
			if (parent == parentParent->_pLeft)
			{
				parentParent->_pLeft = subR;
			}
			else if (parent == parentParent->_pRight)
			{
				parentParent->_pRight = subR;
			}
		}



	}



	//右单旋,与左旋思想类似
	void RotatoR(Node* parent)
	{
		Node* subL = parent->_pLeft;
		Node* subLR = subL->_pRight;
		Node* parentParent = parent->_pParent;

		if (subLR != nullptr)
			subLR->_pParent = parent;
		parent->_pLeft = subLR;

		parent->_pParent = subL;
		subL->_pRight = parent;

		subL->_pParent = parentParent;
		if (parentParent == nullptr)
		{
			_root = subL;

		}
		else
		{
			if (parent == parentParent->_pLeft)
			{
				parentParent->_pLeft = subL;
			}
			else if (parent == parentParent->_pRight)
			{
				parentParent->_pRight = subL;
			}
		}


	}

                
      
;