数据结构——红黑树

1.什么是红黑树?

红黑树是一种特定类型的二叉树,用于组织数据。它是一种平衡二叉查找树(AVL树)的变体,每个结点都带有颜色属性(红色或黑色)。在红黑树中,从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。具体来说,红黑树满足以下性质:

  1. 每个结点要么是红色,要么是黑色。
  2. 根结点是黑色。
  3. 每个叶结点(NIL或空结点)是黑色的。
  4. 如果一个结点是红色的,则它的两个子结点都是黑色的。
  5. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

举个例子

2.红黑树插入的模拟实现

①节点的定义

cpp 复制代码
// 在这里为了方便表示我们先将颜色枚举
// 在红黑树中只有黑与红,即BLACK与RED
enum Color
{
	BLACK,
	RED
};

// 因为节点是公用的,因此设定为struct
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		// 根据红黑树的性质可以知道
		// 要插入的新节点不应该是黑色的,而应该是红色的
		// 如果是黑色的那么就会影响它当前路径的黑色节点总数
		, _color(RED) 
	{}

	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	Color _color;
};

②插入

cpp 复制代码
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;

	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 寻找要插入的位置
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		// 到此处cur已经指向了应该插入的位置,
		// 然后判断应该插入到parent的哪边
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 插入完成后需要对红黑树进行调整
		// 若父节点是黑就无需调整
		// 而当父节点是红就需要进行调整
        // ...
	}

private:
	Node* _root = nullptr;
};

以上面的红黑树为例,在parent为红的情况下,插入之后的情况可以将它们大致分为两种:1. uncle存在且为红;2.uncle不存在或uncle存在且为黑。让我们来分析一下,在这里我们依旧采取先具体后推广到一般的方法

1.uncle存在且为红

先举一个具体例子

抽象图如下

2.uncle不存在或uncle存在且为黑

先举一个uncle不存在的具体例子

再举一个uncle存在且为黑的具体例子

观察上述例子之后可以发现,这两种情况本质上可以当做同一操作,那就是先判断grandpa、parent与cur的关系,然后根据具体情况进行旋转,旋转之后进行变色,因此抽象图如下

综合上面的所有情况,再加上一些细节我们可以得到如下插入代码

cpp 复制代码
bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* cur = _root;
		Node* parent = nullptr;
		// 寻找要插入的位置
		while (cur)
		{
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		// 到此处cur已经指向了应该插入的位置,
		// 然后判断应该插入到parent的哪边
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		// 插入完成后判断一下
		// 若父节点是黑就无需调整
		// 而当父节点是红就需要进行调整
		while (parent && parent->_color == RED)
		{
			Node* grandpa = parent->_parent;
			if (parent == grandpa->_left)
			{
				Node* uncle = parent->_right;
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = parent->_color = BLACK;
					grandpa->_color = RED;

					cur = grandpa;
					parent = cur->_parent;
				}
				else
				{
					if (uncle == nullptr)
					{
						if (parent->_left == cur)
						{
							//              grandpa
							//      parent
							//cur
							RotateR(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							//              grandpa
							//      parent
							//              cur
							RotateL(parent);
							RotateR(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
					}
					else // uncle存在且为黑
					{
						if (parent->_left == cur)
						{
							//              grandpa
							//      parent
							//cur
							RotateR(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
						else
						{
							//              grandpa
							//      parent
							//              cur
							RotateL(parent);
							RotateR(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
					}

					break;
				}
			}
			else // parent == grandpa->_right
			{
				Node* uncle = parent->_left;
				if (uncle && uncle->_color == RED)
				{
					uncle->_color = parent->_color = BLACK;
					grandpa->_color = RED;

					cur = grandpa;
					parent = cur->_parent;
				}
				else
				{
					if (uncle == nullptr)
					{
						if (parent->_left == cur)
						{
							//grandpa
							//         parent
							//cur
							RotateR(parent);
							RotateL(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
						else
						{
							//grandpa
							//        parent
							//               cur
							RotateL(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
					}
					else // uncle存在且为黑
					{
						if (parent->_left == cur)
						{
							//grandpa
							//         parent
							//cur
							RotateR(parent);
							RotateL(grandpa);
							cur->_color = BLACK;
							grandpa->_color = RED;
						}
						else
						{
							//grandpa
							//        parent
							//               cur
							RotateL(grandpa);
							grandpa->_color = RED;
							parent->_color = BLACK;
						}
					}

					break;
				}
			}
		}

		return true;
	}

(注:左右旋参见数据结构------AVL树

3.红黑树的性能分析

红黑树可以在O(log n)时间内做查找、插入和删除,这里的n 是树中元素的数目。红黑树在最坏情况下的运行时间也是非常良好的,并且在实践中是高效的。红黑树的性能受限于它的平衡性,即每个叶子节点到根节点的路径上所包含的黑色节点的数量是相同的,这也被称为黑色完美平衡。保持这种平衡可以确保红黑树的性能稳定。总的来说,红黑树是一种高效的数据结构,适用于许多不同的情况。

4.红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

相关推荐
JSU_曾是此间年少27 分钟前
数据结构——线性表与链表
数据结构·c++·算法
sjsjs1134 分钟前
【数据结构-合法括号字符串】【hard】【拼多多面试题】力扣32. 最长有效括号
数据结构·leetcode
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
昂子的博客2 小时前
基础数据结构——队列(链表实现)
数据结构
lulu_gh_yu3 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
~yY…s<#>4 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
XuanRanDev5 小时前
【每日一题】LeetCode - 三数之和
数据结构·算法·leetcode·1024程序员节
代码猪猪傻瓜coding5 小时前
力扣1 两数之和
数据结构·算法·leetcode
南宫生7 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
weixin_432702267 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论