数据结构——红黑树

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树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

相关推荐
柃歌31 分钟前
【UCB CS 61B SP24】Lecture 7 - Lists 4: Arrays and Lists学习笔记
java·数据结构·笔记·学习·算法
柃歌40 分钟前
【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
java·数据结构·笔记·学习·算法
干炒 牛河1 小时前
数据结构:双链表list
数据结构·list
醉城夜风~2 小时前
[数据结构]顺序表详解
数据结构
Vacant Seat3 小时前
贪心算法-买卖股票的最佳时机
java·数据结构·算法·贪心算法
郑州吴彦祖7724 小时前
数据结构——二叉树经典习题讲解
java·数据结构·算法·leetcode
和光同尘@4 小时前
74. 搜索二维矩阵(LeetCode 热题 100)
数据结构·c++·线性代数·算法·leetcode·职场和发展·矩阵
zl_dfq4 小时前
数据结构之【顺序表简介】
数据结构·顺序表
code bean4 小时前
【C# 数据结构】队列 FIFO
开发语言·数据结构·c#
梅茜Mercy5 小时前
数据结构:栈和队列详解(下)
数据结构