【高级程序设计语言C++】红黑树

  • 1. 红黑树的概念
  • 2. 红黑树的插入
    • 2.1. 情况1
    • 2.2. 情况2
    • 2.3. 情况3
    • 2.4. 插入情况小总结
  • 3. 红黑树与AVL树的对比
  • 4. 红黑树在线生成网站

1. 红黑树的概念

红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在插入和删除操作时通过调整节点的颜色和旋转来保持树的平衡。红黑树的平衡性是通过以下规则来定义和维护的:

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

通过这些规则,红黑树能够保持相对平衡,从而保证了其插入、删除和查找等操作的时间复杂度都能够保持在O(log n)级别。

红黑树图:

【高级程序设计语言C++】红黑树_第1张图片

关于性质的说明

  1. 不可能出现连续两个红节点

【高级程序设计语言C++】红黑树_第2张图片

这种情况是不允许的!

  1. 每条路径上的黑色节点数是相同的。

【高级程序设计语言C++】红黑树_第3张图片

如图中的路径A与路径B,他们黑色节点数是相同的,如果违背了这些性质,红黑树的结构将会被破坏。

2. 红黑树的插入

红黑树本身是二叉搜索树,只不过是在其基础增加了颜色的区分。所以插入是跟二叉搜索树一样的,不过要根据红黑树的规则来调整。

插入大概上分为3种情况:

2.1. 情况1

cur为红,p为红,g为黑,u存在且为红

举个简单例子,如图:

【高级程序设计语言C++】红黑树_第4张图片

这种情况违背了红黑树的规则,有两个连续的红节点,此时就需要调整。

  1. 把parent和uncle变为黑色
  2. 再把grandfather变为红色
  3. 把grandfa给给cur,再继续往上看是否需要调整

调整后的红黑树:

【高级程序设计语言C++】红黑树_第5张图片

根据上面的情况分析给出抽象图:

【高级程序设计语言C++】红黑树_第6张图片

**假设A/B/C/D/E为一个节点,那么C/D/E的节点将会是黑色的,而A/B是红色的。**具体为什么可以参考红黑树的规则想想。

如果此时选择插入一个节点,那么将会出现情况1。

那么此时具体图如下:

【高级程序设计语言C++】红黑树_第7张图片

第一次调整如图:

【高级程序设计语言C++】红黑树_第8张图片

第一次调整结束后,根据调整过程cur会变动,根据情况分析,此时还需要再一次调整。

第二次调整:

【高级程序设计语言C++】红黑树_第9张图片

此时白色圆圈代表的是,这棵树可能只是一颗子树,如果不是子树的的话,根节点的颜色要变黑色。

【高级程序设计语言C++】红黑树_第10张图片

根据抽象图来画具象图,会有很多种情况,但最主要的情况就是cur为红,p为红,g为黑,u存在且为红.

代码如下:

【高级程序设计语言C++】红黑树_第11张图片

2.2. 情况2

cur红且为p左子树,p为红,g为黑,u不存在/u存在且为黑

先举抽象图例子:

【高级程序设计语言C++】红黑树_第12张图片

当A/B/C/D/E都为空的时候,那么uncle节点是不可能存在的。**因为如果uncle此时存在的话,那么就是情况1,但这里讨论的是情况2,情况2要求的是uncle节点要么存在,要么不存在,而如果uncle存在的话,将不符合红黑树结构的要求。**具象图如下:

【高级程序设计语言C++】红黑树_第13张图片

那么此时就是情况2的uncle节点不存在的情况。所需要做的就是进行右单旋操作。调整后如下图:

【高级程序设计语言C++】红黑树_第14张图片

为什么旋转之后,颜色是这样子变化呢?我想要cur和grandfather的颜色为黑,parent为红不行吗?别急,答案在抽象图会出来。

当A/B/C/D/E的高度为1,并且cur节点的颜色为黑色,如下图所示:

【高级程序设计语言C++】红黑树_第15张图片

调整后如下图:

【高级程序设计语言C++】红黑树_第16张图片

此时这种情况就是情况2,要进行旋转,旋转后如下图:

【高级程序设计语言C++】红黑树_第17张图片

总体的旋转如下图所示:

【高级程序设计语言C++】红黑树_第18张图片

情况2的抽象图:

【高级程序设计语言C++】红黑树_第19张图片

经过旋转,但是颜色未变的视图:

【高级程序设计语言C++】红黑树_第20张图片

如上图所见,这里的grandfather节点颜色是不能为黑色的,因为不符合红黑树的结构规则,因此是要将grandfather和cur的颜色变为红色,而parent颜色为黑色,如下图所示:

【高级程序设计语言C++】红黑树_第21张图片

代码如下:

【高级程序设计语言C++】红黑树_第22张图片

2.3. 情况3

cur红且为p右子树,p为红,g为黑,u不存在/u存在且为黑

这里只画A/B/C/D/E为一个节点时的具象图。

【高级程序设计语言C++】红黑树_第23张图片

具体来说就是,当A/B/C/D/E高度为1的时候,先是碰到了情况1,调整之后,变成了情况2,再调整后,又变成了另一个方向的情况2。那么就根据这种种情况,依次旋转即可。

代码如下:

【高级程序设计语言C++】红黑树_第24张图片

最后要记得把根节点的颜色变为黑色:

img

代码实现:

bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		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);
		if (data > parent->_data)
		{
			parent->_pRight = cur;
			cur->_pParent = parent;
		}
		else if (data < parent->_data)
		{
			parent->_pLeft = cur;
			cur->_pParent = parent;
		}
		//新插入的节点默认颜色为红色,所以下面cur颜色都为红
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_pParent;
			if (parent == grandfather->_pLeft)
			{
				Node* uncle = grandfather->_pRight;
				//情况1 p为红,g为黑,u为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_pParent;
				}
				//走到这里,uncle两种情况
				//1. uncle存在且为黑
				//2. uncle不存在
				else
				{
					//情况2
					//如果cur是parent左子树,进行右旋转
					if (cur == parent->_pLeft)
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况3
					else if (cur == parent->_pRight)
					{
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
			else if (parent == grandfather->_pRight)
			{
				Node* uncle = grandfather->_pLeft;
				//情况1 p为红,g为黑,u为红
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					cur = grandfather;
					parent = cur->_pParent;
				}
				//走到这里,uncle两种情况
				//1. uncle存在且为黑
				//2. uncle不存在
				else
				{
					//情况2
					//如果cur是parent左子树,进行右旋转
					if (cur == parent->_pLeft)
					{
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
					//情况3
					else if (cur == parent->_pRight)
					{
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					break;
				}
			}
		}
		_root->_col = BLACK;
		return true;
	}

2.4. 插入情况小总结

  1. 大类上分为两类,一是uncle节点为红色,而是uncle节点为黑色
  2. 根据uncle节点的颜色,又分为两种情况
  3. 如果uncle节点颜色为红色,并且parent节点和cur节点都为红色,那么就是情况1,直接变颜色即可
  4. 如果uncle节点颜色为黑色,并且parent节点和cur节点都为红色,cur是parent的左节点,那么就是情况2,单旋转
  5. 如果uncle节点颜色为黑色,并且parent节点和cur节点都为红色,cur是parent的右节点,那么就是情况3,双旋转
  6. 关键就是看uncle节点是否存在以及uncle节点的颜色

3. 红黑树与AVL树的对比

红黑树和AVL树都是常用的自平衡二叉搜索树,它们的主要目的都是为了保持树的平衡,以提高搜索、插入和删除操作的性能。然而,红黑树和AVL树在平衡的方式和性能方面存在一些差异。

  1. 平衡性:
    • 红黑树:红黑树通过在节点上引入颜色属性,并遵循一组平衡规则来保持平衡。这些规则包括节点的颜色、路径上的黑色节点数量等。红黑树的平衡性相对较弱,可以在维护平衡的同时提供较高的插入和删除性能。
    • AVL树:AVL树通过在节点上维护一个平衡因子(左子树高度减去右子树高度)来保持平衡。AVL树的平衡性相对较强,要求任何节点的平衡因子在-1、0、1之间。这种强平衡性保证了AVL树的高度始终保持在较小的范围内,但可能会导致插入和删除操作的性能稍低。
  1. 插入和删除操作:
    • 红黑树:由于红黑树的平衡性相对较弱,插入和删除操作的性能较好。红黑树在执行这些操作时只需要进行一些颜色调整和旋转操作,时间复杂度为O(log n)。
    • AVL树:由于AVL树的平衡性较强,插入和删除操作可能需要进行更多的旋转操作来保持平衡,因此性能略低于红黑树。插入和删除操作的时间复杂度为O(log n)。
  1. 查询操作:
    • 红黑树和AVL树在查询操作上的性能相似,时间复杂度为O(log n)。它们都具有快速的搜索能力,可以在平衡的树结构中进行高效的查找。
  1. 空间复杂度:
    • 红黑树和AVL树的空间复杂度都是O(n),其中n是树中节点的数量。它们在每个节点上都需要存储额外的信息来维护平衡性。

综上所述,红黑树和AVL树在平衡性和性能方面存在一些差异。选择使用哪种树结构取决于具体应用场景和需求。如果插入和删除操作频繁且对性能要求较高,可以选择红黑树。如果对平衡性要求较高且能够容忍稍低的性能,可以选择AVL树。

4. 红黑树在线生成网站

如果想验证一组数据生成的红黑树是什么样子的,可以用这个网站去看看。这个网站也是从csdn大佬的博客中发现的,这里给大家链接,希望大佬们能够有所收获。

https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

你可能感兴趣的:(c++,算法)