【C++】高阶数据结构 -- 红黑树

主要讲解另一种二叉平衡树 --- 红黑树


目录

[1 红黑树的概念](#1 红黑树的概念)

[1) 红黑树的节点规则](#1) 红黑树的节点规则)

[2) 红黑树的效率](#2) 红黑树的效率)

[2 红黑树的实现](#2 红黑树的实现)

[1) 红黑树的结构](#1) 红黑树的结构)

[2) 红黑树节点的插入](#2) 红黑树节点的插入)

[(1) 红黑树新插入节点的颜色](#(1) 红黑树新插入节点的颜色)

[(2) 红黑树调整平衡过程](#(2) 红黑树调整平衡过程)

[3) 红黑树的查找](#3) 红黑树的查找)

[3 红黑树的验证](#3 红黑树的验证)

[4 总结](#4 总结)


1 红黑树的概念

之前我们学习了第一棵二叉平衡树 --- AVLTree,红黑树也是一种二叉平衡树,只不过 AVL 树是通过平衡因子来使得整棵树达到平衡。而红黑树是通过控制节点的颜色来控制平衡 。在红黑树的所有节点中,存在一个存储位置,用来存储当前节点的颜色,颜色要么是红色,要么就是黑色;其通过对颜色进行约束,使得树中的最长路径不会长于最短路径的两倍,使得整棵树近似为平衡的状态。所以红黑树跟 AVL 树不同的点就是 AVL 树会通过平衡因子控制整棵树严格平衡,但是红黑树通过控制节点颜色来达到近似平衡,并不是非常严格

1) 红黑树的节点规则

红黑树的节点主要有四条规则:

(1) 每个节点不是红色就是黑色

(2) 根节点是黑色的

(3) 如果一个节点是红色的,那么其两个孩子节点必须是黑色的,任何一条路径不会有两个连续的红色节点;但是黑色节点的两个孩子可以继续是黑色节点

(4) 每条路径的黑色节点都是相同的

比如以下的几棵树就是红黑树:

有时候呢,也把 NIL 节点称为是黑色的,这里的 NIL 节点为叶子节点,但是不是我们之前在二叉树中学习的叶子节点,而是我们之前学习的的空节点称为 NIL 节点(也就是 NULL 指针节点),所以有时候一棵红黑树也是这样的:

判断一个数是否是红黑树的时候需要注意一点,就是要树上的所有路径,包括节点为空的路径,黑色节点的个数都要相同。比如下面这课树就不是一棵红黑树:

很显然,这棵树的路径黑色节点为 3。但是在 25 的左孩子路径上,其黑色节点个数只有 2 个,所以其不是一棵红黑树,如果想要其变成一棵红黑树,就要变成以下这棵树:

这样才能确保所有的路径黑色节点个数为 3 个。

那么有了以上规则,红黑树是如何确保最长路径是不超过最短路径的 2 倍的呢?

我们先看一下在红黑树中的最短路径是怎么样的:由于红黑树中的红色节点不能连续,而且每条路径的黑色节点个数都是相同的,那么最短路径就必然是全部为黑色节点的那一条路径,假设其长度为 bl (black length)。

那么最长路径呢?极端情况下,最长路径就是一黑一红间隔组成的一条路径,其为最长路径。由于黑色节点与红色节点是交替出现的,所以最长路径的长度就是 2 * bl。

所以对于任意一条路径来说,其路径长度始终是 bl <= l <= 2 * bl。

2) 红黑树的效率

我们假设 N 是红黑树的总节点个数,L 是最短路径长度,这里的路径长度其实也就是路径的高度。那么按照我们之前计算高度,深度为 L 的最多节点个数为 ,所以对于一棵红黑树来说其总节点个数 N 是 <= N <= ,算下来 <= L<= ,所以 L 仍然是 O(logN) 级别的。

虽然红黑树不像 AVL 树那样完全平衡,但是其达到了近似平衡,而且其查找效率也是 O(logN) 级别的,依然很快。而且由于红黑树不是完全平衡,所以其旋转的次数会比 AVL 树少很多,反而可能会比 AVL 树效率更高。


2 红黑树的实现

这里实现的红黑树,依然是和之前的 AVL 树一样,实现的是不允许有重复值的 key-value 结构的红黑树。

1) 红黑树的结构

红黑树的结构与 AVL 树很像,所以除了之前 BST 中的 _data、_left、_right 指针,由于要进行旋转,所以还有另外的一个 _parent 指针。但是在红黑树中,每个节点还有一个颜色,这里我们采用 enum 枚举类型来代表颜色,在每个节点里面还有一个枚举类型的变量来代表颜色。默认颜色为黑色,因为在红黑树中对于黑色节点几乎没有限制,所以我们将颜色初始化为黑色。

红黑树节点的结构与 AVL 树节点的结构只有一个地方不同,红黑树中是颜色,AVL 树中是平衡因子。

那么树的结构都是一样的了,都只用一个根节点就可以表示整棵树了。

cpp 复制代码
enum Color
{
	BLACK,
	RED
};

template<class K, class V>
struct RBNode
{
	pair<K, V> _data;
	RBNode<K, V>* _left;
	RBNode<K, V>* _right;
	RBNode<K, V>* _parent;
	Color _col;

	RBNode(const pair<K, V>& data)
		:_data(data)
		,_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_col(BLACK)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBNode<K, V> Node;
public:
private:
	Node* _root;
};

2) 红黑树节点的插入

红黑树呢,依然是一棵二叉搜索树,所以插入一个节点,依然还是按照之前 BST 的插入规则来进行插入一个节点:

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

	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (cur->_data.first > data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_data.first < data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
			return false;
	}

	cur = new Node(data);
	if (parent->_data.first > data.first)
		parent->_left = cur;
	else
		parent->_right = cur;

	//不要忘记链接 _parent
	cur->_parent = parent;

	return true;
}

但是在红黑树节点中,还有一个颜色需要更新,就像在 AVL 树中平衡因子需要更新一样。并且通过颜色来进行红黑树的旋转,近似的控制平衡。

(1) 红黑树新插入节点的颜色

首先,我们先来看一下新插入的节点应该是什么颜色呢?如果插入节点是根节点,为了保证第二条原则,插入的根节点肯定是黑色。如果不是根节点,我们假设新插入的节点是黑色,那么其就违反了所有路径黑色节点相同的原则;那么要想所有路径的黑色节点都相同,第一种方法就是在别的每个路径上再插入一个黑色节点,但是显然这是不可能的;要么就是在该路径上减少一个黑色节点,也就是寻找一个黑色节点将其变为红色,但是这样就只能寻找三个连续黑色节点的中间的黑色节点将其变红,不然就会出现两个连续红色节点的情况;而且该中间节点还不能具有另一个孩子,要不然黑色节点还是每个路径都不一样。但是很显然这样的代价很大,所以我们新插入的节点选择为红色

但是新插入的节点如果是红色,其父亲如果也是红色,那么就违反了第三条原则。但是我们可以通过旋转或者变色的策略来进行调整,其代价会比插入节点为黑色而小的多。

(2) 红黑树调整平衡过程

红黑树插入值的过程

(1) 新插入的节点为红色,如果其父亲节点为黑色,那么插入结束,不需要旋转或者变色

(2) 如果父亲为红色,那么就出现了两个连续的红色,这时候我们会通过其叔叔节点(爷爷节点除父亲节点外的另一个孩子)的情况做出对应的处理,详见如下三种情况

在讲解以下三种情况之前,我们需要先说明一个点。如果出现了需要变色或者旋转的情况,其爷爷节点一定是存在的(下面将当前插入的节点称为 cur、其父亲节点称为 parent,爷爷节点称为 grandparent,叔叔节点称为 uncle),因为如果 parent 为红,那么 parent 一定不会是根节点,一定是根节点的孩子节点,那么 grandparent 至少为根节点。

那么为什么会根据 uncle 来判断情况呢?因为如果需要旋转或者变色,那么 cur、parent 一定为红色(parent 为黑色就不需要变色 + 旋转),那么在插入 cur 节点之前,整棵树一定是一棵红黑树,所以当 parent 为红色,那么 grandparent 一定为黑色,此时 cur、parent、grandparent 的颜色都固定了,所以会根据 uncle 节点的颜色来判断怎么处理。

情况一:uncle 存在且为红

如果 uncle 存在且为红色,也就是 parent、cur、uncle 全部为红色,此时只需要变色处理,就是将 parent、uncle 变为黑色,将 grandparent 变为红色:

很显然,这样变色之后,仍然可能是存在两个连续红色节点的情况,所以我们必须继续向上更新颜色,也就是让 grandparent 成为新的 cur,继续向上更新。

显然,这种情况与 cur 是左孩子还是右孩子是没有关系的,以为这种情况只需要进行变色,并不需要进行旋转就能达到红黑树的原则。所以只要 uncle 存在且为红色,那么我们只需要将 parent 与 uncle 变黑,grandparent 变红,再向上更新即可。

情况二:uncle 存在且为黑

如果 uncle 存在且为黑色,那么 cur 一定不是新增节点,且之前一定是黑色节点。因为如果 cur 是新增节点,且是红色节点;那么 uncle 为黑色节点,这样,cur 节点的这条路径上就会少黑色节点,不满足所有路径黑色节点相同。

这种情况下,单纯的变色已经不能解决问题了,我们需要对其进行旋转加变色。接下来我们来看一个抽象图看一下如何将红黑树进行旋转并且每个节点的颜色如何改变。

这是还未插入节点之前的整棵红黑树或者整棵红黑树的一棵子树,也就是 cur 还是黑色的时候,此时所有路径上的黑色节点数量都是 hc + 1。然后在 A 或者 B 中插入了红色节点,一直向上更新,将 cur 由黑色节点变为了红色节点:

此时 A 和 B 中的黑色节点个数变为 hc。此时就发生了 uncle 存在且为黑的情况,此时需要以 grandparent 为旋转点进行右单旋,也就是 RotateR(grandparent):

旋转过程与我们在 AVL 树中讲解过的右单旋过程是一样的:首先让 grandparent 的左孩子,也就是 parent 成为整棵子树的根;之后让 grandparent 成为 parent 的右孩子;再让 parent 的右子树成为 grandparent 的左子树就可以了。

但是变色是不一样的,在右单旋中,为了保证所有路径的黑色节点个数相同,之前都是 hb+1,所以我们只需要把旋转之后整棵子树的根节点,也就是 parent 变黑,把 grandparent 变红,就可以了。

上面是 parent 为 grandparent 的左子树,同时 cur 也是 parent 的左子树的情况,需要右单旋。那么如果 parent 为 grandparent 的右子树,同时 cur 也是 parent 的右子树,此时需要的就是左单旋:

左单旋也是与 AVL 树中的旋转是相同的,只不过颜色是 parent 更新为黑色,grandparent 更新为红色。

可以看到,不管是左单旋还是右单旋,其颜色更新都是相同的,都是 parent 更新为黑色,grandparent 更新为红色。

那么如果 parent 为 grandparent 的左子树(右子树),但是 cur 为 parent 的右子树(左子树)呢?此时就需要进行双旋了:

其中的右左双旋和左右双旋跟 AVL 树中的步骤相同,但是颜色更新相同。只要是双旋,那就是 cur 更新为黑色,parent 更新为红色。

经过旋转更新完之后,整棵子树的每个路径黑色节点的个数都变成了 hc+1,也就是插入之前的节点的个数。所以经过旋转之后就没有必要再往上继续更新了,退出循环即可。

情况三:uncle 不存在

如果 uncle 不存在,那么 cur 一定是一个新增节点,因为如果 cur 不是新增节点,由于 parent 为红色节点,根据第三条原则不能有两个连续的红色节点,所以 cur 一定是一个黑色节点,一旦 cur 为黑色节点,那么 uncle 又不存在,那么经过 uncle 节点的所有路径一定会少黑色节点,这样就不满足在 cur 插入之前是一棵红黑树,所以 cur 一定是新增节点。

这种情况下,也需要进行旋转 + 变色,普通的变色并不能解决问题。

上面展示了 uncle 不存在时,进行右单旋和左右双旋的情况,颜色更新与 uncle 为黑色时进行的颜色更新相同。左单旋与右左双旋也与 uncle 为黑色时相同,这里就不再赘述。与 uncle 存在且为黑一样,旋转之后就不必继续更新了,直接退出循环即可。


我们其实也可以根据另一种标准来进行分类:

a. 不旋转,只变色。很显然这种情况对应着 uncle 存在且为红的情况。

b. 单旋 + 变色。对于 uncle 不存在或者 uncle 存在且为黑时,如果 parent 是 grandparent 的左孩子,同时 cur 也是 parent 的左孩子,那就进行右单旋;如果 parent 是 grandparent 的右孩子,同时 cur 也是 parent 的右孩子,那就进行左单旋。但是不管是哪种单旋,更新方式都是 parent 更新为黑色,grandparent 更新为红色。

c. 双旋 + 变色。对于 uncle 不存在或者 uncle 存在且为黑时,如果 parent 是 grandparent 的左孩子,但是 cur 是 parent 的右孩子,那就进行左右双旋;如果 parent 是 grandparent 的右孩子,但是 cur 也是 parent 的左孩子,那就进行右左双旋。但是不管是哪种双旋,更新方式都是 cur 更新为黑色,grandparent 更新为红色。

最后需要注意一点,在插入节点更改颜色的过程中,最后可能会把根节点更改为红色,在结束之后,需要重新将根节点变为黑色。所以红黑树中黑色节点增加并不是由插入节点而来,而是由根节点将红色节点变为黑色节点而来

代码

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

	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (cur->_data.first > data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_data.first < data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
			return false;
	}

	cur = new Node(data);
	if (parent->_data.first > data.first)
		parent->_left = cur;
	else
		parent->_right = cur;

	//不要忘记链接 _parent
	cur->_parent = parent;
	//新插入节点的颜色为红色
	cur->_col = RED;

	//父亲不为空,并且 parent 颜色为红色时才需要更新颜色
	while (parent && parent->_col == RED)
	{
		//parent 为红色,grandparent 一定存在
		Node* grandparent = parent->_parent;
		if (parent == grandparent->_left)
		{
			Node* uncle = grandparent->_right;
			//如果 uncle 存在且为红色
			if (uncle && uncle->_col == RED)
			{
				//将 uncle 与 parent 变黑,grandparent 变红
				uncle->_col = parent->_col = BLACK;
				grandparent->_col = RED;

				//继续向上更新
				cur = grandparent;
				parent = cur->_parent;
			}
			else
			{
				//uncle 不存在或者 uncle 存在且为黑
				if (cur == parent->_left)
				{
					//不管哪种情况,只要在左边,就进行右单旋
					RotateR(grandparent);

					//将 parent 更新为黑色,grandparent 更新为红色
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				else
				{
					//进行左右双旋
					RotateLR(grandparent);

					//将 cur 更新为黑色,grandparent 更新为红色
					cur->_col = BLACK;
					grandparent->_col = RED;
				}

				//旋转之后,不需要继续更新了
				break;
			}
		}
		else
		{
			Node* uncle = grandparent->_left;
			//如果 uncle 存在且为红色
			if (uncle && uncle->_col == RED)
			{
				//将 uncle 与 parent 变黑,grandparent 变红
				uncle->_col = parent->_col = BLACK;
				grandparent->_col = RED;

				//继续向上更新
				cur = grandparent;
				parent = cur->_parent;
			}
			else
			{
				//uncle 不存在或者 uncle 存在且为黑
				if (cur == parent->_right)
				{
					//不管哪种情况,只要在右边,就进行左单旋
					RotateL(grandparent);

					//将 parent 更新为黑色,grandparent 更新为红色
					parent->_col = BLACK;
					grandparent->_col = RED;
				}
				else
				{
					//进行右左双旋
					RotateRL(grandparent);

					//将 cur 更新为黑色,grandparent 更新为红色
					cur->_col = BLACK;
					grandparent->_col = RED;
				}

				//旋转之后,不需要继续更新了
				break;
			}
		}
	}

	//根节点可能被更新为红色,要重新变为黑色
	_root->_col = BLACK;

	return true;
}

3) 红黑树的查找

红黑树的查找与 BST 逻辑相同,但是红黑树效率为 O(logN)。

cpp 复制代码
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_data.first < key)
		{
			cur = cur->_right;
		}
		else if (cur->_data.first > key)
		{
			cur = cur->_left;
		}
		else
			return cur;
	}

	return nullptr;
}

3 红黑树的验证

验证红黑树,其实就是验证那四条原则就可以了。第一条原则肯定是满足的,因为我们采取的枚举类型。第二条原则只需要判断 _root 是否是黑色的就可以了。第三四条比较麻烦。

对于第三条的验证,我们不采取判断父亲节点和孩子节点是否都是红色的策略,我们采取判断当前节点与其父亲节点是否都是红色节点的策略。这样,当递归到一个节点时,只需要判断该节点颜色是否为红色 && 该节点是否有父亲 && 父亲的节点是否也是红色就可以了。

对于最后一条,我们采取这样的策略:我们首先计算出最左路径中黑色节点的个数 blackNum;然后进行前序遍历,当 root 走到空时,此时代表遍历完了一条路径,然后将当前路径中的黑色节点个数与 blackNum 进行比较,如果不同,直接返回 false;如果相同,那就返回 true。这样,当前序遍历完所有路径,如果都没有返回 false,那就说明是一棵红黑树了。

cpp 复制代码
bool Check(Node* root, int blackNum, const int refNum)
{
	//前序遍历走到空,说明一条路径走完了
	if (root == nullptr)
	{
		if (blackNum != refNum)
		{
			cout << "路径黑色节点不同" << endl;
			return false;
		}

		return true;
	}

	//检查其与其父亲节点是否都是红色
	if (root->_col == RED && root->_parent && root->_parent->_col == RED)
	{
		cout << "存在连续的红色节点" << endl;
		return false;
	}

	if (root->_col == BLACK)
		++blackNum;

	//如果左右子树都是红黑树,那当前子树就是红黑树
	return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}

bool IsRBTree()
{
	//如果是一棵空树,那就是一棵红黑树
	if (_root == nullptr)
		return true;

	if (_root->_col == RED)
	{
		cout << "根节点颜色为红色" << endl;
		return false;
	}

	//计算出最左边路径的黑色节点个数
	int blackNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			blackNum++;

		cur = cur->_left;
	}

	return Check(_root, 0, blackNum);
}

4 总结

以下是红黑树的完整实现代码:

cpp 复制代码
//RBTree.hpp
#pragma once
#include <iostream>

using namespace std;

enum Color
{
	BLACK,
	RED
};

template<class K, class V>
struct RBNode
{
	pair<K, V> _data;
	RBNode<K, V>* _left;
	RBNode<K, V>* _right;
	RBNode<K, V>* _parent;
	Color _col;

	RBNode(const pair<K, V>& data)
		:_data(data)
		,_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_col(BLACK)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBNode<K, V> Node;

	void RotateR(Node* parent)
	{
		Node* Parent = parent->_parent;
		Node* subLR = parent->_left->_right;
		Node* subL = parent->_left;

		//不要忘记更改父亲的指向
		//parent 可能为根节点
		if (parent == _root)
			_root = subL;
		else
		{
			if (parent == Parent->_left)
				Parent->_left = subL;
			else
				Parent->_right = subL;
		}

		//subL 的父亲节点也需要更改
		subL->_parent = Parent;

		//更改左右孩子指向
		parent->_left = subLR;
		subL->_right = parent;

		//不要忘记更改 parent 以及 subLR 的父亲指向
		parent->_parent = subL;
		if (subLR)
			subLR->_parent = parent;
	}

	void RotateL(Node* parent)
	{
		Node* Parent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//不要忘记更改父亲的指向
		//parent 可能为根节点
		if (parent == _root)
			_root = subR;
		else
		{
			if (parent == Parent->_left)
				Parent->_left = subR;
			else
				Parent->_right = subR;
		}

		//subR 的父亲节点也需要更改
		subR->_parent = Parent;

		//更改孩子指向
		subR->_left = parent;
		parent->_right = subRL;

		//不要忘记更改 parent 与 subRL 的父亲指向
		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;
	}

	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//先以 subL 为 parent 进行左单旋
		RotateL(subL);
		//再以 parent 为 parent 进行右单旋
		RotateR(parent);
	}

	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//先以 subR 为 parent 进行右单旋
		RotateR(subR);
		//再以 parent 为 parent 进行左单旋
		RotateL(parent);
	}

	bool Check(Node* root, int blackNum, const int refNum)
	{
		//前序遍历走到空,说明一条路径走完了
		if (root == nullptr)
		{
			if (blackNum != refNum)
			{
				cout << "路径黑色节点不同" << endl;
				return false;
			}

			return true;
		}

		//检查其与其父亲节点是否都是红色
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		if (root->_col == BLACK)
			++blackNum;

		//如果左右子树都是红黑树,那当前子树就是红黑树
		return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
	}

	int _Height(Node* root) const
	{
		if (root == nullptr)
			return 0;

		int LeftHeight = _Height(root->_left);
		int RightHeight = _Height(root->_right);

		return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;
	}

	size_t _size(Node* root) const
	{
		//利用递归来算节点个数
		if (root == nullptr)
			return 0;

		return _size(root->_left) + _size(root->_right) + 1;
	}

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

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_data.first > data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_data.first < data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
				return false;
		}

		cur = new Node(data);
		if (parent->_data.first > data.first)
			parent->_left = cur;
		else
			parent->_right = cur;

		//不要忘记链接 _parent
		cur->_parent = parent;
		//新插入节点的颜色为红色
		cur->_col = RED;

		//父亲不为空,并且 parent 颜色为红色时才需要更新颜色
		while (parent && parent->_col == RED)
		{
			//parent 为红色,grandparent 一定存在
			Node* grandparent = parent->_parent;
			if (parent == grandparent->_left)
			{
				Node* uncle = grandparent->_right;
				//如果 uncle 存在且为红色
				if (uncle && uncle->_col == RED)
				{
					//将 uncle 与 parent 变黑,grandparent 变红
					uncle->_col = parent->_col = BLACK;
					grandparent->_col = RED;

					//继续向上更新
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					//uncle 不存在或者 uncle 存在且为黑
					if (cur == parent->_left)
					{
						//不管哪种情况,只要在左边,就进行右单旋
						RotateR(grandparent);

						//将 parent 更新为黑色,grandparent 更新为红色
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//进行左右双旋
						RotateLR(grandparent);

						//将 cur 更新为黑色,grandparent 更新为红色
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					//旋转之后,不需要更新了
					break;
				}
			}
			else
			{
				Node* uncle = grandparent->_left;
				//如果 uncle 存在且为红色
				if (uncle && uncle->_col == RED)
				{
					//将 uncle 与 parent 变黑,grandparent 变红
					uncle->_col = parent->_col = BLACK;
					grandparent->_col = RED;

					//继续向上更新
					cur = grandparent;
					parent = cur->_parent;
				}
				else
				{
					//uncle 不存在或者 uncle 存在且为黑
					if (cur == parent->_right)
					{
						//不管哪种情况,只要在右边,就进行左单旋
						RotateL(grandparent);

						//将 parent 更新为黑色,grandparent 更新为红色
						parent->_col = BLACK;
						grandparent->_col = RED;
					}
					else
					{
						//进行右左双旋
						RotateRL(grandparent);

						//将 cur 更新为黑色,grandparent 更新为红色
						cur->_col = BLACK;
						grandparent->_col = RED;
					}

					//旋转之后,不需要继续更新了
					break;
				}
			}
		}

		//根节点可能被更新为红色,要重新变为黑色
		_root->_col = BLACK;

		return true;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_data.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_data.first > key)
			{
				cur = cur->_left;
			}
			else
				return cur;
		}

		return nullptr;
	}

	bool IsRBTree()
	{
		//如果是一棵空树,那就是一棵红黑树
		if (_root == nullptr)
			return true;

		if (_root->_col == RED)
		{
			cout << "根节点颜色为红色" << endl;
			return false;
		}

		//计算出最左边路径的黑色节点个数
		int blackNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				blackNum++;

			cur = cur->_left;
		}

		return Check(_root, 0, blackNum);
	}

	size_t Height() const
	{
		return _Height(_root);
	}

	size_t size() const
	{
		return _size(_root);
	}

private:
	Node* _root = nullptr;
};

//testRBTree.cpp
#include "RBTree.hpp"
#include <vector>

int main()
{
	//这里直接采用随机值测试
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	srand(time(NULL));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	//测试性能
	size_t begin1 = clock();
	RBTree<int, int> t;
	for (auto& e : v)
	{
		t.Insert({ e, e });
	}
	size_t end1 = clock();

	cout << "Insert: " << end1 - begin1 << endl;
	cout << "is RBTree: " << t.IsRBTree() << endl;

	cout << "Height: " << t.Height() << endl;
	cout << "Size: " << t.size() << endl;

	//查找
	size_t begin2 = clock();
	for (size_t i = 0; i < N; i++)
	{
		t.Find(rand() + i);
	}
	size_t end2 = clock();
	cout << "Find: " << end2 - begin2 << endl;

	return 0;
}

我们并没有实现红黑树的删除,如果有兴趣可以自己学习并实现一下。红黑树是另一种二叉搜索树,比起 AVL 树用平衡因子控制平衡,红黑树是采用颜色节点的个数来控制平衡,其对于平衡并没有那么严格;但正是因为没有那么严格,其旋转次数会少很多。因此,有时候红黑树的效率反而会比 AVL 树更高。所以 STL 中的 map 和 set 才会选择使用红黑树来作为底层结构,后面的 map 与 set 模拟实现也是通过封装红黑树来实现的。

相关推荐
智者知已应修善业5 小时前
【查找字符最大下标以*符号分割以**结束】2024-12-24
c语言·c++·经验分享·笔记·算法
划破黑暗的第一缕曙光5 小时前
[数据结构]:5.二叉树链式结构的实现1
数据结构
91刘仁德5 小时前
c++类和对象(下)
c语言·jvm·c++·经验分享·笔记·算法
青桔柠薯片5 小时前
数据结构:单向链表,顺序栈和链式栈
数据结构·链表
diediedei5 小时前
模板编译期类型检查
开发语言·c++·算法
mmz12076 小时前
分治算法(c++)
c++·算法
XiaoFan0126 小时前
将有向工作流图转为结构树的实现
java·数据结构·决策树
一切尽在,你来6 小时前
C++多线程教程-1.2.1 C++11/14/17 并发特性迭代
开发语言·c++
睡一觉就好了。6 小时前
快速排序——霍尔排序,前后指针排序,非递归排序
数据结构·算法·排序算法