红黑树

红黑树是一种二叉搜索树,通过颜色属性保持相对平衡,以提高插入效率。相较于AVL树,红黑树在插入时避免频繁旋转,更适合常有增删操作的场景。本文介绍了红黑树的概念、结构、性质,以及插入操作的详细步骤和源码解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

红黑树的相关概念

概念

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

结构图

在这里插入图片描述
红黑树结构和普通的二叉树差不多,但是多了一个header,header是根节点的父节点,根节点又是header的父节点,而header的左右孩子分别是整个树的最左节点和最右节点,也就是整个树的最小值和最大值。

性质

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

和AVL树的对比

AVL树,是一个十分平衡的搜索二叉树,他的所有子树的左右高度差是不会超过1的,因此在插入和删除的时候,需要做很多的旋转操作,甚至每插入几个结点就要旋转,因此它的插入效率很低。换来的是高效的查询效率,因为它的高度是很平衡的,因此它的查找效率是最接近O(logN)的。

红黑树,也是一个平衡的搜索二叉树,但是没有AVL数平衡,红黑树的平衡是指:其最长路径中节点个数不会超过最短路径节点个数的两倍。红黑树也构成了一个比较平衡的二叉树,因此插入节点时候的效率会比AVL树效率高一些,因为它不需要做频繁的旋转操作,它是通过本身自带的颜色属性来控制各个子树的高度,来达到一个相对的平衡。查找效率也是趋近于O(logN),虽然没有AVL数的查找效率高,但是也是logN级别的,而且让插入时候的操作变的简单。

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

红黑树的插入操作

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

  1. 按照二叉搜索的树规则插入新节点:树的左边小于数的右边
  2. 检测新节点插入后,红黑树的性质是否造到破坏
    因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

前提:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点

情况一: cur为红,p为红,g为黑,u存在且为红
在这里插入图片描述
解决方式:
将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

情况二: cur为红,p为红,g为黑,u不存在/u为黑
在这里插入图片描述
解决方式:
第一步
2.1 p为g的左孩子,cur为p的左孩子,则进行右单旋转
2.2 p为g的右孩子,cur为p的右孩子,则进行左单旋转
第二步
p、g变色–p变黑,g变红

情况三: cur为红,p为红,g为黑,u不存在/u为黑(情况三其实是变形的情况二,情况二是p和cur都在左边或者都在右边,情况三是p和cur不在一边)
在这里插入图片描述

解决方式:
第一步
3.1 p为g的左孩子,cur为p的右孩子,则针对p做左单旋转
3.2 p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
第二步
这里你会发现第一步完成后的结构变成了情况二的结构,所以这里的执行情况二的步骤即可

插入操作的源码

下面我插入的值是一个键值对,这是为了模拟封装set和map而制作的数据结构,在这里先不介绍如何封装set和map

bool Insert(std::pair<K, V> data)
		{
			//说明还没有结点
			if (_head->_parent == _head)
			{
				pNode root = new Node(data);
				root->_colour = BLACK;
				_head->_left = _head->_right = _head->_parent = root;
				root->_parent = _head;
				return true;
			}
			pNode cur = _head->_parent;
			pNode parent = nullptr;
			//找到合适的位置插入
			while (cur)
			{
				parent = cur;
				if (cur->_data.first > data.first)
				{
					cur = cur->_left;
				}
				else if (cur->_data.first < data.first)
				{
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}
			cur = new Node(data);

			//左边
			if (parent->_data.first > data.first)
			{
				parent->_left = cur;
			}
			//右边
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;

			//分情况调整
			//如果父节点是黑色,则不需要调整
			while (cur != _head->_parent && cur->_parent->_colour == RED)
			{
				parent = cur->_parent;
				pNode gParent = parent->_parent;
				pNode uncle = nullptr;
				//获取叔叔结点
				if (gParent->_left == parent)
				{
					uncle = gParent->_right;
				}
				else
				{
					uncle = gParent->_left;
				}

				//如果叔叔结点存在,而且颜色是红色:
				//向上逐层更新,暂不需要旋转
				if (uncle && uncle->_colour == RED)
				{
					//更新颜色
					parent->_colour = BLACK;
					uncle->_colour = BLACK;
					gParent->_colour = RED;
					//更新结点
					cur = gParent;
				}
				//如果叔叔结点不存在或者颜色是黑色:
				//则需要旋转
				else
				{
					//如果cur是parent的左孩子、parent是gParent的左孩子 或者 cur是parent的右孩子、parent是gParent的右孩子:
					//进行单旋
					if ((gParent->_left == parent && parent->_left == cur) || (gParent->_right == parent && parent->_right == cur))
					{
						if (parent->_left == cur)
						{
							Rotate_Right(gParent);
						}
						else
						{
							Rotate_Left(gParent);
						}
						gParent->_colour = RED;
						parent->_colour = BLACK;
						break;
					}
					//如果三代的关系不是全是左或者全是右:
					//双旋
					else
					{
						//下面只做了单旋,第二次旋转放在下一次循环
						//这是双旋的场景,但是旋一次后就变成了上面的单旋的场景了,所以cur不更新成gParent
						if (parent->_right == cur)
						{
							Rotate_Left(parent);
							//因为交换完之后parent在下面cur在上面
							//所以要把两个指向更改一下
							std::swap(cur, parent);
						}
						else
						{
							Rotate_Right(parent);
							//因为交换完之后parent在下面cur在上面
							//所以要把两个指向更改一下
							std::swap(cur, parent);
						}
					}
				}
			}
			//根始终是黑色的
			_head->_parent->_colour = BLACK;
			_head->_left = Left_Most();
			_head->_right = Right_Most();
			return true;
		}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值