【C++】红黑树

一、红黑树的概念

红黑树是一颗⼆叉搜索树,它的每个结点增加⼀个存储位来表示结点的颜⾊,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有⼀条路径会比其它路径长出2倍(最长路径不会超过最短路径的2倍),因而是接近平衡的(它没有AVL树那样严格)。

二、红黑树的规则

一颗红黑树必须满足一下几个特征:

  1. 每个结点不是红色就是黑色

  2. 根结点是黑色的

  3. 如果一个结点是红色的,则它的两个孩子结点必须是黑色的,也就是说任意⼀条路径不会有连续的红色结点。(黑色结点的孩子可能是红也可能是黑,红色结点的孩子只能是黑)

  4. 对于任意一个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量黑色结点。

上述规则可能有点不知所云,我们先来看看红黑树的模样(对照着上述规则):

上述第一张图中的红黑树一共有几条路径?

我猜你会说5条。 其实答案是9条,如下图:

知道一颗红黑树的路径数是很重要的,因为我们要根据路径数来判断每条路径上的黑色结点个数是否相等。

《算法导论》等书籍上补充了⼀条每个叶子结点(NIL)都是黑色 的规则。它这里所指的叶子结点不是传统的意义上的叶子结点,而是我们说的空结点 ,有些书籍上也把NIL叫做外部结点。NIL是为了方便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了NIL结点,所以我们知道一下这个概念即可。

NIL都是黑色既不违反规则3,也不违反规则4。

三、如何确保红黑树中最长路径不超过最短路径的2倍?

根据规则4可知,从根开始每条路径黑色结点的数量都相等。

假设每条路径有X个黑色结点,极端条件下(并不是一定会发生):最短路径为:X(全黑) ,最长路径为:2*X(一黑一红...)。上面第一个图就是对应的极端场景。

在极端极端条件下,最长路径等于最短路径的2倍,所以在其它非极端情况下,红黑树确保没有⼀条路径会比其它路径长出2倍。

换一种更为"规矩"的说法:

由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径

就是全是黑色结点的路径,假设最短路径长度为bh(black height)。

由规则2和规则3可知,任意一条路径不会有连续的红色结点,所以极端场景下,最长的路径就是一黑一红间隔组成,那么最长路径的长度为2*bh。

综合红黑树的4点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都

存在的。假设任意一条从根到NULL结点路径的长度为x,那么bh<=x<=2*bh。

四、红黑树的效率

假设N是红黑树中结点数量,h是最短路径的长度,那么 ,由此推出
,也就是意味着红黑树增删查改最坏也就是走最长路径,那么时间复杂度还是

红黑树的表达相对AVL树要抽象⼀些,AVL树通过高度差直观的控制了平衡。红黑树通过4条规则的颜色约束,间接的实现了近似平衡,它们效率都是同⼀档次的。红黑树和AVL树的高度都不是严格的logN,但是AVL树相比于红黑树高度更接近logN(AVL树高度差更严格),理论上,AVL树的效率要比红黑树好一些,但是由于它们的时间复杂度都在logN这个层次,而CPU处理速度太快了,所以它们两个的效率没有什么区别。

五、红黑树的实现

1、整体框架

cpp 复制代码
// 枚举值表⽰颜⾊
enum Colour
{
	RED,
	BLACK
};

//这里我们按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;	//这里加入parent指针是为了控制平衡
	Colour _col; //每个结点的颜色

	//构造新结点
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

//主逻辑
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
	public:

		//具体接口实现
		//...

	private:
	Node* _root = nullptr; //红黑树根结点指针
};

2、红黑树的插入

首先我们要明确红黑树在插入新结点前是红黑树,虽然这句话有点像废话,但请谨记。

红黑树插入一个值的大致过程:

  1. 首先按⼆叉搜索树规则进行插入,插入后我们只需要观察是否符合红黑树的4条规则。
  2. 如果是空树插入,新增结点是黑色结点。如果是非空树插入,新增结点必须是红色结点,因为非空树插入,新增黑色结点就破坏了规则4,规则4是难维护的。
  3. 非空树插入后,新增结点必须是红色结点,如果此时父亲结点是黑色的,则没有违反任何规则,插入结束。
  4. 非空树插入后,新增结点必须红色结点,如果父亲结点是红色的,则违反规则3。这种情况是可以维护的,具体维护过程如下分析:

为了方便叙述,我们把新增结点标识为c(cur),c的父亲标识为p(parent),p的父亲标识为g(grandfather),p的兄弟标识为u(uncle)。

进一步分析,c是红色,p为红(第4个过程),那么g必为黑(插入前是红黑树),这三个颜色都固定了,关键的变化是看u的情况,需要根据u分为以下几种情况分别处理:

1、u存在且为红(不发生旋转)

c为红,p为红,g为黑,u存在且为红 ,则将p和u变黑,g变红 。在把g当做新的c,继续往上更新。

分析:因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加⼀个黑色结点,g再变红,相当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题,需要继续往上更新是因为,g是红色,如果g的父亲还是红色,那么就还需要继续处理;如果g的父亲是黑色,则处理结束了;如果g就是整棵树的根,再把g变回黑色。

这种情况只变色,不旋转。所以无论c是p的左还是右,p是g的左还是右,都是上面的变色处理方式。上图可以抽象为下图(可以不看):

2、u不存在(会发生旋转)

大家认为这样直接改颜色正确吗?

这样是不对的,直接改颜色会导致18->10->NULL这条路径上少一个黑色。

这种情况需要"旋转+变色":

旋转前,c这个结点是新增结点还是由黑变到红色的呢?

c这个结点一定是新增结点,c如果是由黑变红,那么就不满足红黑树的第4个规则了,所以它一定是新增的。

分析:p必须变黑,才能解决问题连续红色结点的问题,u不存在,这里单纯的变色就会违反规则4,所以需要上述过程的"旋转+变色"。

3、u存在且为黑(会发生旋转)

上述图是抽象图,d这颗子树中的hb(bh,black height)一定比a、b、e、f多1,这样才符合规则4。

先看中间状态(忽略起始状态),c这个结点是新增结点还是由黑变到红色的呢?

答案一定是它是由原来的和变为红的,因为如果它是新增结点,那么插入前,该树不符合红黑树的规则4,所以u存在且为黑的情况下,c一定是由原来的黑变为红的。

然后再看整个过程就清晰许多了,从中间状态到结束,首先父亲是一定要变黑的,那么就会违反规则4,必须要解决,那就"旋转+变色"。

4、特殊情况(针对旋转,双旋)

如果c插入到p的右边,仅仅单旋是解决不了问题的,需要双旋来解决问题。大家如果不清楚旋转的详细过程,可以去看这篇文章 ->【C++】AVL树

红黑树插入的大体逻辑已经全部讲解完毕,看似很难,实则一点不简单,但是静观己心,探寻规律,只是复杂,不难理解。

插入的整体代码如下:

cpp 复制代码
bool Insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				_root->_col = BLACK;

				return true;
			}

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

			cur = new Node(kv);
			cur->_col = RED; //插入的新结点必须是红色
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;

			// 链接父亲
			cur->_parent = parent;

			// 父亲是红色,那么就会出现连续的红色节点,需要处理
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;

				if (parent == grandfather->_left) //叔叔(u)在右边
				{
					//   g
					// p   u
					Node* uncle = grandfather->_right;
					//第一种情况:叔叔存在且为红
					if (uncle && uncle->_col == RED)
					{
						// 变色
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						// 继续往上处理
						cur = grandfather;
						parent = cur->_parent;
					}
					//第二、三种情况:叔叔不存在或者叔叔存在且为黑
					else  
					{
						if (cur == parent->_left) //右单旋
						{
							//     g
							//   p    (u)
							// c
							RotateR(grandfather);
							parent->_col = BLACK; //更新颜色
							grandfather->_col = RED;
						}
						else  //双旋
						{
							//     g
							//   p   (u)
							//     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;   //更新颜色
							grandfather->_col = RED;
						}

						break; //旋转完直接结束,不需要向上再进行判断了
					}
				}
				else  //叔叔(u)在左边,主要逻辑和上面一样,只不过方向变了
				{
					//   g
					// u   p
					Node* uncle = grandfather->_left;
					//叔叔存在且为红 -> 变色即可
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						//继续往上处理
						cur = grandfather;
						parent = cur->_parent;
					}
					else //叔叔(u)不存在或者存在且为黑
					{
						// 情况二、三:叔叔不存在或者存在且为黑 -> 旋转+变色
						//	   g
						// (u)   p
						//         c
						if (cur == parent->_right)  //单旋
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else   //双旋
						{
							//     g
							// (u)   p
							//     c
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;  //旋转完直接结束,不需要向上更新
					}
				}
			}
			_root->_col = BLACK;  //特殊处理,插入结束后不管三七二十一直接让根结点为黑(主要是针对情况1)
			return true;
		}

旋转代码:

cpp 复制代码
//右单旋
		void RotateR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;

			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;

			Node* pParent = parent->_parent;

			subL->_right = parent;
			parent->_parent = subL;

			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (pParent->_left == parent)
					pParent->_left = subL;
				else
					pParent->_right = subL;

				subL->_parent = pParent;
			}
		}
		//左单旋
		void RotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;
			parent->_right = subRL;
			if (subRL)
				subRL->_parent = parent;

			Node* parentParent = parent->_parent;
			subR->_left = parent;
			parent->_parent = subR;
			if (parentParent == nullptr)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (parent == parentParent->_left)
					parentParent->_left = subR;
				else
					parentParent->_right = subR;
				subR->_parent = parentParent;
			}
		}

3、红黑树的高度

cpp 复制代码
public:
//树的高度
int Height()
{
	return _Height(_root);
}
private:
int _Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}

4、红黑树的结点数量

cpp 复制代码
public:
//结点数量
int Size()
{
	return _Size(_root);
}
private:
int _Size(Node* root)
{
	if (root == nullptr)
		return 0;

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

5、红黑树中序遍历

cpp 复制代码
public:
//中序遍历
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
private:
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_InOrder(root->_right);
}

6、红黑树判断平衡

这里获取最长路径和最短路径,检查最长路径不超过最短路径的2倍是不可行的,因为就算满足这个条件,红黑树也可能颜色不满足规则,当前暂时没出问题,后续继续插入还是会出问题的。所以我们还是去检查4点规则,满足这4点规则,一定能保证最长路径不超过最短路径的2倍。

  1. 对于规则1,因为每个结点都有颜色,天然实现保证了颜色不是黑色就是红色。

  2. 对于规则2,直接检查根即可。

  3. 对于规则3,前序遍历检查,遇到红色结点查孩子结点的颜色,因为孩子有两个,且不一定存在,所以查起来不太方便,但遇到红色结点,反过来检查父亲的颜色 就方便多了。

  4. 对于规则4,前序遍历,遍历过程中用形参记录根到当前结点的blackNum(黑色结点数量),前序遍历遇到黑色结点就++blackNum,走到空就计算出了⼀条路径的黑色结点数量。再以任意一条路径黑色结点数量作为参考值(refNum),依次比较即可。

代码实现:

cpp 复制代码
public:
//判断平衡
bool IsBalance()
{
	if (_root == nullptr)
		return true;

	if (_root->_col == RED)
		return false;

	// 参考值
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
		{
			++refNum;
		}
		cur = cur->_left;
	}

	return Check(_root, 0, refNum);
}
private:
bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		// 前序遍历走到空时,意味着一条路径走完了
		//cout << blackNum << endl;
		if (refNum != blackNum)
		{
			cout << "存在黑色结点的数量不相等的路径" << endl;
			return false;
		}
		return true;
	}

	// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
	if (root->_col == RED && root->_parent->_col == RED)
	{
		cout << root->_kv.first << "存在连续的红色结点" << endl;
		return false;
	}

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

	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}

7、查找结点

cpp 复制代码
//查找结点
Node* Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_kv.first < key)
			cur = cur->_right;
		else if (cur->_kv.first > key)
			cur = cur->_left;
		else
			return cur;
	}
	return nullptr;
}

六、红黑树和AVL树的效率对比

cpp 复制代码
//测试AVL树和红黑树的效率对比
void TestTree1()
{
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	size_t end2 = clock();

	size_t begin22 = clock();
	RBTree<int, int> rbt;
	for (auto e : v)
	{
		rbt.Insert(make_pair(e, e));
	}
	size_t end22 = clock();

	cout << "AVL Insert:" << end2 - begin2 << endl;
	cout << "RB Insert:" << end22 - begin22 << endl;

	cout <<"AVL IsBalance:"<< t.IsBalanceTree() << endl;
	cout << "RB IsBalance:" << rbt.IsBalance() << endl;


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

	cout << "RB Height:" << rbt.Height() << endl;
	cout << "RB Size:" << rbt.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t.Find(e);
	}
	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/
	size_t end1 = clock();
	cout << "AVL Find:" << end1 - begin1 << endl;

	size_t begin11 = clock();
	// 确定在的值
	for (auto e : v)
	{
		rbt.Find(e);
	}
	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/
	size_t end11 = clock();
	cout << "RB Find:" << end11 - begin11 << endl;
}

int main()
{
	TestTree1();
	return 0;
}

运行代码:

总的来说它们的效率差别不大,平时中红黑树用的比较多。

七、源码

1、RBTree.h

cpp 复制代码
#pragma once
#include <iostream>
using namespace std;

//枚举值表示颜色
enum Colour
{
	RED,
	BLACK
};

//这里我们按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
	pair<K, V> _kv;
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;	//这里加入parent指针是为了控制平衡(方便找"爷爷"结点)
	Colour _col; //每个结点的颜色

	//构造新结点
	RBTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
	{}
};

//主逻辑
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
	public:
		//右单旋
		void RotateR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;

			parent->_left = subLR;
			if (subLR)
				subLR->_parent = parent;

			Node* pParent = parent->_parent;

			subL->_right = parent;
			parent->_parent = subL;

			if (parent == _root)
			{
				_root = subL;
				subL->_parent = nullptr;
			}
			else
			{
				if (pParent->_left == parent)
					pParent->_left = subL;
				else
					pParent->_right = subL;

				subL->_parent = pParent;
			}
		}
		//左单旋
		void RotateL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;
			parent->_right = subRL;
			if (subRL)
				subRL->_parent = parent;

			Node* parentParent = parent->_parent;
			subR->_left = parent;
			parent->_parent = subR;
			if (parentParent == nullptr)
			{
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{
				if (parent == parentParent->_left)
					parentParent->_left = subR;
				else
					parentParent->_right = subR;
				subR->_parent = parentParent;
			}
		}

		//具体接口实现
		//...
		bool Insert(const pair<K, V>& kv)
		{
			if (_root == nullptr)
			{
				_root = new Node(kv);
				_root->_col = BLACK;

				return true;
			}

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

			cur = new Node(kv);
			cur->_col = RED; //插入的新结点必须是红色
			if (parent->_kv.first < kv.first)
				parent->_right = cur;
			else
				parent->_left = cur;

			// 链接父亲
			cur->_parent = parent;

			// 父亲是红色,那么就会出现连续的红色节点,需要处理
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;

				if (parent == grandfather->_left) //叔叔(u)在右边
				{
					//   g
					// p   u
					Node* uncle = grandfather->_right;
					//第一种情况:叔叔存在且为红
					if (uncle && uncle->_col == RED)
					{
						// 变色
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						// 继续往上处理
						cur = grandfather;
						parent = cur->_parent;
					}
					//第二、三种情况:叔叔不存在或者叔叔存在且为黑
					else  
					{
						if (cur == parent->_left) //右单旋
						{
							//     g
							//   p    (u)
							// c
							RotateR(grandfather);
							parent->_col = BLACK; //更新颜色
							grandfather->_col = RED;
						}
						else  //双旋
						{
							//     g
							//   p   (u)
							//     c
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;   //更新颜色
							grandfather->_col = RED;
						}

						break; //旋转完直接结束,不需要向上再进行判断了
					}
				}
				else  //叔叔(u)在左边,主要逻辑和上面一样,只不过方向变了
				{
					//   g
					// u   p
					Node* uncle = grandfather->_left;
					//叔叔存在且为红 -> 变色即可
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;

						//继续往上处理
						cur = grandfather;
						parent = cur->_parent;
					}
					else //叔叔(u)不存在或者存在且为黑
					{
						// 情况二、三:叔叔不存在或者存在且为黑 -> 旋转+变色
						//	   g
						// (u)   p
						//         c
						if (cur == parent->_right)  //单旋
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else   //双旋
						{
							//     g
							// (u)   p
							//     c
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;  //旋转完直接结束,不需要向上更新
					}
				}
			}
			_root->_col = BLACK;  //特殊处理,插入结束后不管三七二十一直接让根结点为黑(主要是针对情况1)
			return true;
		}

		//中序遍历
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		//树的高度
		int Height()
		{
			return _Height(_root);
		}

		//结点数量
		int Size()
		{
			return _Size(_root);
		}

		//判断平衡
		bool IsBalance()
		{
			if (_root == nullptr)
				return true;

			if (_root->_col == RED)
				return false;

			// 参考值
			int refNum = 0;
			Node* cur = _root;
			while (cur)
			{
				if (cur->_col == BLACK)
				{
					++refNum;
				}
				cur = cur->_left;
			}

			return Check(_root, 0, refNum);
		}

		//查找结点
		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (cur->_kv.first < key)
					cur = cur->_right;
				else if (cur->_kv.first > key)
					cur = cur->_left;
				else
					return cur;
			}
			return nullptr;
		}
		
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

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

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


	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			// 前序遍历走到空时,意味着一条路径走完了
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色结点的数量不相等的路径" << endl;
				return false;
			}
			return true;
		}

		// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "存在连续的红色结点" << endl;
			return false;
		}

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

		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}
private:
	Node* _root = nullptr; //红黑树根结点指针
};

2、Test.cpp

cpp 复制代码
#include "RBTree.h"
#include "AVLTree.h"
#include <vector>

void TestRBTree1()
{
	RBTree<int, int> t;
	// 常规的测试用例
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	// 特殊的带有双旋场景的测试用例
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	for (auto e : a)
		t.Insert({ e, e });

	t.InOrder();
	cout << t.IsBalance() << endl;
}

//测试AVL树和红黑树的效率对比
void TestTree2()
{
	const int N = 10000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));
	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	size_t end2 = clock();

	size_t begin22 = clock();
	RBTree<int, int> rbt;
	for (auto e : v)
	{
		rbt.Insert(make_pair(e, e));
	}
	size_t end22 = clock();

	cout << "AVL Insert:" << end2 - begin2 << endl;
	cout << "RB Insert:" << end22 - begin22 << endl;

	cout <<"AVL IsBalance:"<< t.IsBalanceTree() << endl;
	cout << "RB IsBalance:" << rbt.IsBalance() << endl;


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

	cout << "RB Height:" << rbt.Height() << endl;
	cout << "RB Size:" << rbt.Size() << endl;

	size_t begin1 = clock();
	// 确定在的值
	for (auto e : v)
	{
		t.Find(e);
	}
	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/
	size_t end1 = clock();
	cout << "AVL Find:" << end1 - begin1 << endl;

	size_t begin11 = clock();
	// 确定在的值
	for (auto e : v)
	{
		rbt.Find(e);
	}
	// 随机值
	/*for (size_t i = 0; i < N; i++)
	{
		t.Find((rand() + i));
	}*/
	size_t end11 = clock();
	cout << "RB Find:" << end11 - begin11 << endl;
}

int main()
{
	//TestRBTree1();
	TestTree2();
	return 0;
}

其中"AVL.h"见 -> 【C++】AVL树

八、结语

本篇内容到这里就结束了,主要讲了红黑树的定义以及插入接口的实现逻辑,希望对大家有所帮助,祝生活愉快,下篇再见!

相关推荐
诚丞成28 分钟前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
东风吹柳1 小时前
观察者模式(sigslot in C++)
c++·观察者模式·信号槽·sigslot
A懿轩A2 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
大胆飞猪3 小时前
C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
c++
1 9 J3 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
夕泠爱吃糖3 小时前
C++中如何实现序列化和反序列化?
服务器·数据库·c++
长潇若雪3 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象
染指11105 小时前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
Code out the future5 小时前
【C++——临时对象,const T&】
开发语言·c++
sam-zy6 小时前
MFC用List Control 和Picture控件实现界面切换效果
c++·mfc