【数据结构】红黑树

一、红黑树的性质

红黑树是一种接近平衡的二叉搜索树,在上一篇博客AVL树中说过,AVL树是通过平衡因子来控制平衡的,右子树与左子树高度差的绝对值不超过1,而红黑树则是通过颜色来实现近似平衡的,在红黑树中,最长路径结点个数不超过最短路径结点个数的两倍。

红黑树的性质:

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

2)根结点为黑色

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

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

5)每个叶子结点(NIL结点)都是黑色的

红黑树,如其名,是由一个个红色结点和黑色结点组成的,注意它的根结点必须是黑色的。关于第三点,通俗点说就是不能有连续的红色结点,但是可以有连续的黑色结点。关于第五点,这里的NIL结点是指空结点。

下面给一颗红黑树的示例:

思考:为什么 红黑树能保证最长路径结点个数不超过最短路径结点个数的两倍?

可以想象一下,每条路径的黑色结点数量是一样的,最极端的情况下,最短路径应该是全是黑色结点,最长路径为黑色结点与红色结点交替,这样最长路径结点数是最短路径结点数的两倍,而一般情况下是很少有一条路径中全是黑色结点的,所以,最长路径结点数不超过最短路径结点数的两倍。

二、红黑树的插入

红黑树与AVL数非常类似,插入时也会涉及到旋转,有了前边AVL树的基础,红黑树的插入就相对简单多了,接下来详细讲解红黑树的插入操作。

结点插入的位置与之前二叉搜索树一样,遍历红黑树,小就走左子树,大就走右子树,最终到要插入的位置,需要考虑的是结点应该给什么颜色,根据红黑树的性质,每条路径的黑色结点数量是一致的,若是新插入的结点给红色,那么其他路径黑色结点数量就要增加,这显然很麻烦,所以新增结点给红色。这时候就需要注意是否会有连续的红色结点,如果说插入结点后,其父结点是黑,直接插入即可,如果其父结点为红,就违背了性质三,这时候就需要旋转了。

下边重点讲解出现连续红结点怎么旋转:

当当前结点为红色,且其父结点为红色时,有连续红结点,这时父结点的父亲一定是黑色的,否则在前边就已经违反了规则,需要考虑的就是叔叔结点的颜色,主要有两种情况,即叔叔结点也为红和叔叔结点为黑或不存在。

1)当叔叔结点也为红时,直接改变颜色即可

将parent和uncle变成黑色,再将grandfather变成红色。把grandfather变成红色后,需要继续向上调整,因为可能grandfather的父结点也为红,就又出现了连续红结点,

草图:

2)当叔叔结点不存在或者为黑色时

这种情况其实是有情况①变来的,因为变色了,所以父结点和叔叔结点颜色不一样,这时候需要旋转,与前边AVL树一样,涉及到单旋和双旋,当只是单纯一边高时,单旋即可,否则需要双旋。

①当叔叔结点不存在时

如果是单纯一边高,即parent在grandfather的左(右),cur也在parent的左(右),这时候单旋转后改变颜色即可。

草图:

将parent变黑,grandfather变红。

如果不是单纯一边高,即parent在grandfather的左(右),cur在parent的右(左),这时候要先先以parent旋转,再以grandfather旋转,最后改变颜色。

草图:

将cur变黑,grandfather变红。

②当叔叔结点为黑色时

与叔叔结点不存在的情况一样,随着grandfather移动即可,这里只给出草图。

在上面的草图中,都是以parent为grandfather的左来展开讨论的,如果parent为grandfather的右也是同理的,若是一边高单旋即可,否则则需要旋转两次,因为两种情况其实是成镜像对称的,所以颜色变化也是一样的。所以插入的代码书写的整体思路就是:先遍历树找到插入位置,再分parent为grandfather的左和右两种情况来讨论,在每种情况中再细分uncle为红色和uncle不存在或为黑两种情况(再分情况旋转一次或者两次)。

实现代码:

cpp 复制代码
bool Insert(const T& data)
{
	if (_pHead == nullptr)
	{
		_pHead = new Node(data);
		_pHead->_col = BLACK; //根结点为黑
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _pHead;
	while (cur)
	{
		if (data > cur->_data)
		{
			parent = cur;
			cur = cur->_pRight;
		}
		else if (data < cur->_data)
		{
			parent = cur;
			cur = cur->_pLeft;
		}
		else
		{
			return false;
		}
	}

	//非根结点,插入红色结点
	cur = new Node(data);
	cur->_col = RED;
	if (data > parent->_data)
	{
		parent->_pRight = cur;
	}
	else
	{
		parent->_pLeft = cur;
	}
	cur->_pParent = parent;

	//parent为红,要变色
	while(parent && parent->_col == RED)
	{
		Node* grandfather = parent->_pParent;
		//看叔叔是红还是黑,或者不存在
		if (parent == grandfather->_pLeft)
		{
			Node* uncle = grandfather->_pRight;
			if (uncle && uncle->_col == RED)
			{
				//uncle为也红,将parent和uncle变黑,grandfather变红
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续往上处理
				cur = grandfather;
				parent = cur->_pParent; //parent可能为空,while条件中要判断
			}
			else
			{
				//uncle存在为黑或不存在

				//一边高
				//      grandfather(黑)
				//   parent(红)       uncle(黑或不存在)
				// cur(红)
				if (cur == parent->_pLeft)
				{
					RotateR(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//双旋
					//     g
					//p          u
					//      c

					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;

				}

				break; //子树根变成黑的了,不需要再向上调整了
			}
		}
		else
		{
			Node* uncle = grandfather->_pLeft;
			//    g
			//u       p
			//            c
			if (uncle && uncle->_col == RED)
			{
				parent->_col = BLACK;
				uncle->_col = BLACK;
				grandfather->_col = RED;

				//继续往上处理
				cur = grandfather;
				parent = cur->_pParent;
			}
			else
			{
				//uncle存在为黑或不存在

				//一边高
				//      g
				//   u       p
				//               c
				if (cur == parent->_pRight)
				{
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					//双旋
					//     g
					//u          p
					//      c

					RotateR(parent);
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;

				}

				break; //子树根变成黑的了,不需要再向上调整了
			}
		}


	}

	//如果循环是因为parent为空结束,cur为根且为红,要变黑
	//为了方便,直接不管什么情况,都将根变黑
	_pHead->_col = BLACK; 
	return true; 

}

最后,整体代码(左旋右旋):

cpp 复制代码
enum Color
{
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& data = T())
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		,_col(RED)
	{}

	RBTreeNode<T>* _pLeft;
	RBTreeNode<T>* _pRight;
	RBTreeNode<T>* _pParent;
	T _data;
	Color _col;
};


template<class T>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	RBTree()
		:_pHead(nullptr)
	{}

	RBTree(const T& data)
	{
		_pHead = new Node(data);
		_pHead->_pLeft = nullptr;
		_pHead->_pRight = nullptr;
		_pHead->_pParent = nullptr;
	}
	
	//bool Insert(const T& data);由于上边写过,这里不给出
	
	
private:

	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft; //可能为空
		Node* pParentParent = pParent->_pParent;

		subR->_pLeft = pParent;
		pParent->_pParent = subR;

		pParent->_pRight = subRL;
		if (subRL)
			subRL->_pParent = pParent;

		if (pParentParent == nullptr)
		{
			_pHead = subR;
			subR->_pParent = nullptr;
		}
		else
		{
			if (pParentParent->_pLeft == pParent)
			{
				pParentParent->_pLeft = subR;
			}
			else
			{
				pParentParent->_pRight = subR;
			}

			subR->_pParent = pParentParent;

		}
	}



	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		Node* pParentParent = pParent->_pParent;

		pParent->_pLeft = subLR;
		if (subLR)
			subLR->_pParent = pParent;

		subL->_pRight = pParent;
		pParent->_pParent = subL;

		//连接
		if (pParentParent == nullptr)
		{
			_pHead = subL;
			subL->_pParent = nullptr;
		}
		else
		{
			if (pParentParent->_pLeft == pParent)
			{
				pParentParent->_pLeft = subL;
			}
			else
			{
				pParentParent->_pRight = subL;
			}

			subL->_pParent = pParentParent;
		}


	}
	
private:
	Node* _pHead;
};
相关推荐
_OLi_2 小时前
力扣 LeetCode 106. 从中序与后序遍历序列构造二叉树(Day9:二叉树)
数据结构·算法·leetcode
我明天再来学Web渗透2 小时前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
冉佳驹3 小时前
数据结构 ——— 希尔排序算法的实现
c语言·数据结构·算法·排序算法·希尔排序
是糖不是唐3 小时前
代码随想录算法训练营第五十三天|Day53 图论
c语言·数据结构·算法·图论
DC妙妙屋3 小时前
11.19.2024刷华为OD
数据结构·链表·华为od
理论最高的吻7 小时前
98. 验证二叉搜索树【 力扣(LeetCode) 】
数据结构·c++·算法·leetcode·职场和发展·二叉树·c
灼华十一9 小时前
算法编程题-排序
数据结构·算法·golang·排序算法
一子二木生三火9 小时前
IO流(C++)
c语言·开发语言·数据结构·c++·青少年编程
先鱼鲨生9 小时前
排序【数据结构】【算法】
数据结构·算法·排序算法
疯狂的代M夫10 小时前
数据结构 【带环链表2】
数据结构·链表