当前位置: 首页 > article >正文

RBTree--红黑树

1.红黑树的概念

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

红黑树也是三叉链结构,如下图所示。不过它没有平衡因子,取之代之的是颜色。

2.红黑树的性质

1)红黑树中的每个节点不是红色就是黑色;

2)根节点是黑色的;

3)如果一个节点是红色的,则该节点的两个孩子节点都是黑色的,即没有连续的红色节点;

4)对于每个节点,从该节点到其所有后代节点的简单路径上,均包含形态数目的黑色节点;

5)每个叶子节点都是黑色的(此处的叶子节点指的是空结点)。

红黑树中要求最长路径不超过最短路径的2倍,是通过颜色来控制的。由红黑树的性质可知,红黑树的根节点是黑色的、每条路径上都有相同数量的黑色节点、没有连续的红色节点,那么红黑树中最短的路径就是全黑色节点的路径,最长路径是黑色节点+相同数量的红色节点,即一黑一红间隔的路径。

3.红黑树节点的定义

enum Colour
{
	BLACK,
	RED,
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<k, V>* _left;//节点的左孩子
	RBTreeNode<K, V>* _right;//节点的右孩子
	RBTreeNode<K, V>* _parent;//节点的父亲节点(红黑树需要旋转,为了实现简单给出该字段)

	pair<K, V> _kv;//节点的值域

	Colour _col;//节点的颜色

	RBTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{}
};

4.红黑树的插入操作

4.1 红黑树插入操作分析

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

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

2.新节点插入后,检测红黑树的性质是否被破坏,因为新节点的默认颜色是红色。因此,如果其双亲节点的颜色是黑色,没有违反红黑树的任何性质,则不需要进行调整。但是当新插入节点的父亲节点的颜色为红色时,就违反了红黑树性质3不能存在连续的红色节点。此时,需要对红黑树分情况来讨论。

为什么默认新插入的节点为红色?因为插入新节点时,就涉及破坏规则2(红黑树中没有连续的红节点);还是规则3(红黑树每条路径都有相同数量的黑节点)。
首先,插入红色节点时,不一定会破坏规则2(如果插入节点的父亲节点是黑色节点);即使破坏了规则2,新插入的节点是红色节点也只会影响一条路径。
如果新插入的节点是黑色的,会影响二叉树的所有路径,因为红黑树的每条路径都要有相同数量的黑色节点。故默认新插入的节点为红色。

约定:cur为当前节点,p为父亲节点,g为祖父节点,u为叔叔节点。

情况1:cur为红,p为红,g为黑(此时,p为红,则g肯定为黑),u存在且为红。

解决方式:将p和u改为黑色,g改为红色,然后将g当成cur,继续向上调整。

注意:这里所看到的树,可能是一棵完整的树,也可能是一棵子树。如下图所示,将节点p和u改为黑色,将节点g改为红色。

①如果g是根节点,调整完成后,需要将g改为黑色。

②如果g是子树,g一定有双亲节点,且g的双亲节点如果为红色,需要继续向上调整。

情况2:cur为红,p为红,g为黑,u不存在/u存在且为黑。

此时,u的情况有两种:

1.u节点不存在,此时cur一定为新插入的节点,如果cur不是新插入的节点,则cur和p一定有一个节点的颜色是黑色,此时就不满足性质4(每条路径黑色节点的个数相同)。

a)u不存在的情况比较简单,假如p是g的左子树,cur是p的左子树,将节点g右单旋,p变黑,g变红。

b)假如p是g的右子树,cur是p的右子树,将节点g左单旋,p变黑,g变红。

2.如果u节点存在,则其颜色一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是cur的子树在调整的过程中将cur节点的颜色由黑色改为红色。则可知该情况是由情况1向上调整时转化而来。如下图所示。

a)u存在且为黑,当p为g的左孩子、cur为p的左孩子,则节点g进行右单旋,p变黑、g变红。抽象图如下:

具象图如下:

b)u存在且为黑,当p是g的右子树,cur是p的右子树时,g左单旋,p变黑,g变红。抽象图如下。

具象图如下:

情况3:cur为红,p为红,g为黑,u不存在/u存在且为黑

a)p为g的左孩子,cur为p的右孩子,则针对p做左单旋,转换成了情况2。

b)p为g的右孩子,cur为p的左孩子,则针对p做右单旋,转换成了情况2。

4.2 红黑树插入操作代码

bool Insert(const pair<K, V>& kv)
{
	//1、按二叉搜索树的规则插入节点
	//如果二叉树为空,则将新插入的节点作为根节点
	if (_root == nullptr)
	{
		_root = new Node(kv);
		_root->_col = BLACK;//红黑树的根节点为黑色
		return true;
	}
		
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(kv);
	if (kv.first > parent->_kv.first)
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else if (kv.first < parent->_kv.first)
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

	//cur->_col = RED;
	//这里默认新插入的节点是红色节点,为什么呢?
	//因为插入新节点时,就涉及破坏规则2(红黑树中没有连续的红节点);还是规则3(红黑树每条路径都有相同数量的黑节点)。
	//首先,插入红色节点时,不一定会破坏规则2(如果插入节点的父亲节点是黑色节点);即使破坏了规则2,新插入的节点是
	//红色节点也只会影响一条路径。
	//如果新插入的节点是黑色的,会影响二叉树的所有路径,因为红黑树的每条路径都要有相同数量的黑色节点。

	//情况1:cur节点为红色、parent节点为红色、grandfather为黑色、uncle节点存在且为红色;
	//情况2:uncle节点不存在
	//情况3:uncle节点存在且为黑
	while (parent&&parent->_col==RED)
	{
		//cur节点的父亲节点parent是红色,此时parent节点不可能是根节点
		//此时看cur节点的叔叔节点uncle的颜色。
		Node* grandfather = parent->_parent;
		if (grandfather->_left == parent)
		{
			Node* uncle = grandfather->_right;
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//如果grandfather不是根节点,继续往上处理。
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//情况3:双旋;先parent节点左旋,转换为情况2
				if (parent->_right == cur)
				{
					RotateL(parent);
					swap(parent, cur);
				}

				//情况2:情况2也可能是情况3进行左单旋之后,需要再进行右单旋
				RotateR(grandfather);
				grandfather->_col = RED;
				parent->_col = BLACK;

				break;
			}
		}
		else
		{
			Node* uncle = grandfather->_left;
			//情况1:uncle存在、且为红
			//情况2 or 情况3:uncle不存在 or uncle存在、且为黑
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				//如果grandfather不是根节点,继续往上处理
				cur = grandfather;
				parent = cur->_parent;
			}
			else
			{
				//情况3
				if (cur == parent->_left)
				{
					RotateR(parent);
					swap(cur, parent);
				}

				//情况2
				RotateL(grandfather);

				grandfather->_col = RED;
				parent->_col = BLACK;
			}
		}
	}

	//最终将根节点变为黑色
	_root->_col = BLACK;

	return true;
}

5.红黑树节点的查找

//2、红黑树查找节点
Node* Find(const pair<K, V>& kv)
{
	Node* cur = _root;
	while (cur)
	{
		if (kv.first > cur->_kv.first)
		{
			cur = cur->_right;
		}
		else if (kv.first < cur->_kv.first)
		{
			cur = cur->_left;
		}
		else
		{
			return cur;
		}
	}

	return nullptr;
}

红黑树完整代码可参考:红黑树的简单实现


http://www.kler.cn/a/402936.html

相关文章:

  • TCP vs UDP:如何选择适合的网络传输协议?
  • 实际工程里为什么不用面向过程编程而是用面向对象编程
  • AI Large Language Model
  • 高质量代理池go_Proxy_Pool
  • 小程序24-滚动效果:scroll-view组件详解
  • Mono Repository方案与ReactPress的PNPM实践
  • MySQL 架构概览
  • 开源一个练手的项目,就叫新闻助手吧
  • vue中动态渲染静态图片资源
  • 如何用GPT-4o解读视频
  • 线性回归Tensorflow实现
  • net某高校社交学习平台的设计与实现
  • 多传感器融合感知算法-后融合
  • 【Linux】开发工具(yum)
  • Uniapp运行环境判断和解决跨端兼容性详解
  • Android开发实战班 - Android开发基础之 Kotlin语言基础与特性
  • ThinkPHP中使用ajax接收json数据的方法
  • 深度学习-循环神经网络RNN
  • 【c++入门】打开新世界大门之初遇c++
  • 一种构建网络安全知识图谱的实用方法
  • RDD触发算子:一些常用的触发算子(count、foreach、saveAsTextFile、first)
  • Linux 常用命令大全
  • 7 设计模式原则之合成复用原则
  • LabVIEW三针自动校准系统
  • java:简单小练习,面积
  • (Linux)搭建静态网站——基于http/https协议的静态网站