红黑树【c++实现】

1.红黑树的概念

红黑树其实还是一个二叉搜索树,不一样的是,红黑树多了一个颜色的概念

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

红黑树的参考图:

image-20241011000953802

2.红黑树的性质

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

  2. 根节点是黑色的

  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

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

满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍

为什么?

因为超过了就一定会破坏性质4:对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

3.红黑树节点的定义

template<class K, class V>
struct RedBlackTreeNode
{
	RedBlackTreeNode<K, V>* _left;
	RedBlackTreeNode<K, V>* _right;
	RedBlackTreeNode<K, V>* _parent; // 三叉链

	pair<K, V> _kv; // 键值对

	Colour _col; // 节点的颜色

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

};

4.红黑树的插入

首先红黑树还是一个二叉搜索树,因此还是得先按照二叉搜索树的规则去插入,在插入的时候判断红黑树的规则是否被破坏了,破坏了就进行调整。从而得到红黑树

下面是先按照二叉搜索树插入的代码:

template<class K, class V>
class RedBlackTree
{
	typedef RedBlackTreeNode<K, V> Node;
public:

	// 库中的返回值是pair类型,后面模拟实现set和map再用pair,现在先用bool
	bool Insert(const pair<K, V> kv)
	{
		// 红黑树首先是二叉搜索树,因此还得按照二叉搜索树的规则去插入,在插入的时候按照红黑树的规则再去修改

		// 先判断树是不是空的
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_clo = BLACK; // 根节点一定是黑的
			return true;
		}

		// 不是空的就要找到插入的位置
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			// 判断kv的K是比cur的K是小还是大,小往左走,大就往右走
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// 二叉搜素树不允许数据重复
				return false;
			}
		}

		// 找到了要插入的位置的父亲节点
		// 判断要插入的是parent的左边还是右边
		Node* newnode = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = newnode;
			newnode->_parent = parent;
		}
		else
		{
			parent->_right = newnode;
			newnode->_parent = parent;
		}

		//判断新增节点该是红的还是黑的
		newnode->_clo = RED; // 红的好处理,黑的就不好处理了,黑的会影响每个路径的黑节点都是一样的这个规则

		// 插入红的节点,需要判断是否合法。这里会有多种情况,需要一一分析

		return ture;
	}

private:
	Node* _root = nullptr;

};

检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:如果其父亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的父亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:

插入的时候红黑树会有多种情况,因此需要对多种情况进行分析,才能写出代码

第一种情况:

插入的节点cur默认为红,p为红、g为黑、u存在且为红

image-20241011191019604

这个情况的处理思路如下图所示:

image-20241011144532646

总结:

  1. 默认插入节点是红色节点,因此需要判断其父亲节点是否是红节点。黑的不处理,红的就处理
  2. 处理的时候判断u是否存在且是否为红,若u为红,则将p、u一起变黑,将g变红;
  3. 变完之后还得判断g是否为根节点。
  4. 如果是g是根节点,则将其改为黑即可
  5. 如果g不是根节点。说明这只是一个子树,那么就要判断g的父亲节点是黑的还是红的。如果是黑的,就说明合法,停止更新。如果是红的,就将g的位置当成cur再次更新、因为此时的g就相当于插入了一个红节点

第5点的:如果是红的,就将g的位置当成cur再次更新、因为此时的g就相当于插入了一个红节点

这个是怎么理解呢?看下面这图:

image-20241011145625902

需要注意的是:

image-20241011150317614

只有第一种情况是这样的,第二种情况就不是这样了。这里不涉及旋转,自然不在意cur的位置,但是情况2、3涉及旋转就不能不管cur插入的位置了

上面是第一种情况的一种具体的情况,下面这个图是第一种情况的具象图:

三角形代表子树

image-20241011151221519

第二种情况:

插入的节点cur默认为红,p为红、g为黑、u为黑\u不存在

这里u为黑和u不存在是两种情况

image-20241011154518003

注意:

这里u为黑情况的cur是怎么变红的,其实就是第一种情况的其中一种处理方式

image-20241011155958038

但是这两种情况的处理方式都是一样的,因此我们归类为第二种情况

处理方法都是单旋+变色

p为g的左孩子,cur为p的左孩子,则进行右单旋;

相反,p为g的右孩子,cur为p的右孩子,则进行左单旋

p、g变色–p变黑,g变红

过程如下图所示:

image-20241011162227987

注意:

上图的情况是用右单旋,但是旋转是具体情况具体分析的,可能会是左单旋也可能是右单旋

要注意图片里的只是一种具体的情况,树可能是很复杂的,但是将问题拆分出来都是这样处理的。

下面是第二种情况的具象图:

image-20241011163359916

总结:

  1. 插入节点cur(也可能是下面子树更新导致cur变红)默认是红节点,此时判断父亲p是黑还是红,如果是红就要调整
  2. 判断u是否存在,如果存在且为红那就是第一种情况处理。如果不存在就第二种情况处理。此时不可能存在u为黑的情况。因为cur是插入节点
  3. 如果第一种情况处理完,还要向上处理,即g的父亲是红,此时可能会碰到u是黑的情况,也是第二种情况处理。单旋+变色

注意:

这里判断是第二种情况还是第三种情况是通过判断cur是p的左孩子还是右孩子判断的。

左孩子就是第二种情况,单旋即可

右孩子就是第三种情况,需要双旋

第三种情况:

第三种情况是第二种情况的变种。

仍然是插入的节点cur默认为红,p为红、g为黑、u为黑\u不存在

但是cur的位置不一样了。

image-20241011165102191

这里u为黑和u不存在是两种情况,但是处理方法是一样的,因此归类为第三种情况

处理方法都是双旋+变色

p为g的左孩子,cur为p的右孩子,则进行左右双旋;

相反,p为g的右孩子,cur为p的左孩子,则进行右左双旋

g、cur变色–cur变黑,g变红

第三张情况和第二种情况的区别就是cur插入的位置不一样

采取双旋+变色的解决方法

解决过程的图:

image-20241011171553232

image-20241011171531001

注意:

这里可能不只是左右双旋,也可能是右左双旋,具体情况具体分析

上面只是一个具体情况,实际调整的红黑树可能会非常复杂。

下面是具象图:

image-20241011182755835

总结:

  1. 插入节点cur(也可能是下面子树更新导致cur变红)默认是红节点,这个时候判断父亲是否为红
  2. 如果为红,就判断u是什么情况,第三种情况的u是不存在或者u为黑。
  3. 第三种情况,cur是p的右孩子,这个时候就采取双旋+变色

注意:

这里判断是第二种情况还是第三种情况是通过判断cur是p的左孩子还是右孩子判断的。

左孩子就是第二种情况,单旋即可

右孩子就是第三种情况,需要双旋

代码实现:

插入的过程可能会用到旋转。旋转的代码和AVLTree的是一样的。

旋转代码:

	// 左单旋
	void RotateL(Node* parent)
	{
		// 这里要结合博客和笔记来理解旋转的过程才好理解代码
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//	左单旋 【将subR的左孩子变成parent的右孩子,parent变成subR的左孩子】
		parent->_right = subRL;
		subR->_left = parent;

		Node* ppNode = parent->_parent; // 防止后面subR链接不到parent->_parent,这里要做个保存
		// 处理父亲指针的关系
		parent->_parent = subR;
		if (subRL) // 防止subR的左孩子是空的情况
			subRL->_parent = parent;

		// subR的父亲指针也需要变化
		// 这里会有两种情况
		// 1.左单旋之后,subR直接是根节点
		if (parent == _root)
		{
			// 原本parent是根节点,左单旋之后subR变成跟节点
			_root = subR;
			subR->_parent = nullptr;
			return;
		}
		else //2.parent不是根节点,左单旋完毕之后subR仍然是子树
		{
			// 这个情况说明原本parent是一个子树,左单旋后subR也是一个子树
			// 这个时候就要链接上原本parent的_parent,上面存储起来是ppNode

			// 但是这个时候还得判断左单旋之后的subR应该是ppNode的左子树还是右子树
			if (parent == ppNode->_left)
			{
				subR->_parent = ppNode;
				ppNode->_left = subR;
			}
			else
			{
				subR->_parent = ppNode;
				ppNode->_right = subR;
			}
		}
	}

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

		//旋转的时候要注意处理节点之间的关系
		//右单旋【subL的右孩子变成parent的左孩子,parent变成subL的右孩子】
		parent->_left = subLR;
		subL->_right = parent;

		Node* ppNode = parent->_parent; // 防止后面subR链接不到parent->_parent,这里要做个保存
		// 处理父亲节点之间的关系
		parent->_parent = subL;
		if (subLR) // 防止subL的右孩子是空的情况
			subLR->_parent = parent;

		// subL的父亲指针也需要变化,其父亲节点与subL的关系也要连接
		// 这里会有两种情况
		// 1.右单旋之后,subL直接是根节点
		if (_root == parent) // 旋转之前的parent是根节点
		{
			subL->_parent = nullptr;
			_root = subL;
			return;
		}
		else //2.parent是原先的一个子树,左单旋完毕之后subL仍然是子树
		{
			// 判断原先的parent是其父节点ppNode的左子树还是右子树
			if (parent == ppNode->_left)
			{
				// subL替代之后,是ppNode的左子树
				ppNode->_left = subL;
				subL->_parent = ppNode; //处理父亲指针
			}
			else
			{
				// subL替代之后,是ppNode的右子树
				ppNode->_right = subL;
				subL ->_parent = ppNode; //处理父亲指针
			}

		}
	}

红黑树的插入接口的实现代码:

	// 库中的返回值是pair类型,后面模拟实现set和map再用pair,现在先用bool
	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)
		{
			// 判断kv的K是比cur的K是小还是大,小往左走,大就往右走
			if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// 二叉搜素树不允许数据重复
				return false;
			}
		}

		// 出循环,cur为nullptr。
		// parent节点就是cur要插入的位置的父节点
		// 判断要插入的是parent的左边还是右边
		cur = new Node(kv);
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		//判断新增节点该是红的还是黑的
		cur->_col = RED; // 红的好处理,黑的就不好处理了,黑的会影响每个路径的黑节点都是一样的这个规则

		// 插入红的节点,需要判断是否合法。

		// 先判断插入节点的父亲是否为红。红才需要调整,黑色就不需要调整了
		while (parent && parent->_col == RED) // 前面的parent是为了防止父亲不存在了,不然对nullptr解引用直接崩溃
		{
			Node* grandfather = parent->_parent; //进这个循环,g就一定存在
			
			// 先讨论左侧情况
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				// 这里会有3种情况,需要一一分析 【结合博客和笔记理解(比如下面出现的p、q、u)】
				// 1.cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 只需要变色,不需要旋转
					grandfather->_col = RED; // 祖先变红
					// 父亲和叔叔变黑
					parent->_col = BLACK;
					uncle->_col = BLACK;

					// 判断祖先节点g是否为根 
					if (_root == grandfather)
					{
						// 直接让g变黑就完成了。停止更新,退出循环
						grandfather->_col = BLACK;
						return true;
					}

					// 判断 g的父亲是否为黑的
					if(grandfather->_parent && grandfather->_parent->_col == BLACK)
					{
						// 直接让g变黑就完成了。停止更新,退出循环
						grandfather->_col = BLACK;
						return true;
					}

					// 走到这里说明g的父亲节点是红的,要继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在或者u为黑 都进入该分支
				{
					// 走到这里根据cur的位置,可以分成第二种和第三种情况
					// 但是第三种情况可以转化为第二种情况,因此我们先判断是不是第三种情况,是的话就转换
					// 然后后面统一按第二种情况来处理
	
					// 3.cur是p的右孩子,p是g的左孩子 ,p为红,g为黑、u不存在/u为黑
					// 第三种情况就是双旋+变色
					if (cur == parent->_right)
					{
						// p是g的左孩子
						// 因此是左右双旋 + 变色处理
						// 这里先左单旋,然后交换cur和p(不是交换节点,只是将cur和p这两个指针变量交换)
						// 这样转化为第二种情况处理,即右单旋+变色即可
						RotateL(parent); // 左单旋
						swap(parent, cur); // 转化为第二种情况
					}

					// 要注意这里第二种情况可能是第三种情况转化来的
					// 2.cur是p的左孩子,p是g的左孩子,p为红,g为黑、u不存在/u为黑
					// 第二种情况就是单旋+变色
					RotateR(grandfather); // 右单旋
					grandfather->_col = RED;
					parent->_col = BLACK; // 原先是cur变黑。但是上面指针变量交换了
					
					// 处理完这里可以直接退出循环
					// 因为旋转完之后,一定符合红黑树的规则,
					// 即便这里处理的是一个子树,子树符合规则了,整棵树也就符合了红黑树的规则
					break;
				}

			}
			else // parent位于g的右孩子
			{
				Node* uncle = grandfather->_left;
				// 这里会有3种情况,需要一一分析 【结合博客和笔记理解(比如下面出现的p、q、u)】
				// 1.cur为红,p为红,g为黑,u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 只变色不旋转【g变红,p和u变黑】
					grandfather->_col = RED; 
					uncle->_col = parent->_col = BLACK;

					// 判断祖先节点g是否为根 
					if (_root == grandfather)
					{
						// 直接让g变黑就完成了。停止更新,退出循环
						grandfather->_col = BLACK;
						return true;
					}

					// 判断 g的父亲是否为黑的
					if (grandfather->_parent && grandfather->_parent->_col == BLACK)
					{
						// g父亲为黑,就不用调整了,g就为红即可
						return true;
					}

					// 走到这里说明g的父节点是红的,继续向上调整
					cur = grandfather;
					parent = grandfather->_parent;

				}
				else // u不存在或者u为黑
				{
					// 2.cur是p的右孩子,p是g的右孩子 ,p为红,g为黑、u不存在/u为黑
					// 第二种情况的解决方法就是左单旋+变色
					// 3.cur是p的左孩子,p是g的右孩子 ,p为红,g为黑、u不存在/u为黑
					// 第二种情况的解决方法就是右左双旋+变色

					// 走到这里根据cur的位置,可以分成第二种和第三种情况
					// 但是第三种情况可以转化为第二种情况,因此我们先判断是不是第三种情况,是的话就转换
					// 然后后面统一按第二种情况来处理
					if (parent->_left == cur)
					{
						// 右单旋 然后交换cur和parent的位置,转化为第二种情况
						RotateR(parent);
						swap(parent, cur); //(不是交换节点,只是将cur和p这两个指针变量交换)
					}

					// 统一按第二种情况处理 【这里的第二种情况可能是第三种转化来的】
					// 左单旋 + 变色
					RotateL(grandfather);
					parent->_col = BLACK; // 原先是cur变黑。但是上面指针变量交换了
					grandfather->_col = RED;

					// 旋转之后就可以直接退出而不考虑上面的情况了。
					// 因为旋转完之后,一定符合红黑树的规则,
					// 即便这里处理的是一个子树,子树符合规则了,整棵树也就符合了红黑树的规则
					break;
				}
			}
		}

		// 插入完毕,返回true
		return true;
	}

这个插入代码的实现中,需要双旋的第三种情况,我将其拆分为两个单旋,即一个单旋后,交换指针变量达到第二种情况,按照第二种情况处理、这样不需要写双旋代码了。

如下图所示:

image-20241013085530263

image-20241013085604357

这样后面进行变色处理的时候要记得指针变量交换过的问题。

5.红黑树的删除

删除看这个博客的分析

红黑树 - Never - 博客园 (cnblogs.com)

6.红黑树的查找

这里查找就跟之前的二叉搜索树一样查找就行了

代码如下:

	// 红黑树的查找
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			// 判断key比cur的key大还是小。大往右边走,小往左边走
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if(cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				// 走到这里key相同,找到了,那就返回
				return cur;
			}
		}

		// 走到这里就是没找到
		return nullptr;
	}

7.红黑树的验证

要验证红黑树分两步:

  • 验证是二叉搜索树【中序遍历是有序的】

通过中序遍历来判断是否为二叉搜索树

//中序遍历
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;

	// 中序遍历——左子树、根、右子树
	_InOrder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_InOrder(root->_right);
}


//cpp中一般实现递归都要通过子函数
// 因为外边调用这个中序遍历接口的时候没办法直接传一个_root进来,_root是私有的
void InOrder()
{

	_InOrder(_root);
	//_InOrder(this->_root); // 等价于上面的
	cout << endl;
}
  • 验证是否符合红黑树的规则

要检查三个规则:

  1. 根节点必须是黑的
  2. 不能出现连续两个的红节点
  3. 每个路径的黑节点的个数都必须相同

代码如下:

	bool _IsValidRBTree(Node* root, size_t k, size_t blackCount)
	{
		// 判断当前根节点走到空后,k和blackCount是否相等
		if (root == nullptr)
		{
			if (k == blackCount)
			{
				return true;
			}
			else
			{
				cout << "违反红黑树规则:每个路径的黑色节点个数都要相同\n";
				return false;
			}
		}

		// 根节点没有走到空就继续统计当前路径黑色节点出现的次数
		if (root->_col == BLACK)
			k++;

		// 判断当前节点与父节点是否都是红的
		Node* parent = root->_parent;
		if (parent && parent->_col == RED && root->_col == RED) //前面的parent防止第一次进来是根节点,parent是不存在的
		{
			cout << "违反红黑树规则:不允许有两个连续红节点\n";
			return false;
		}

		// 递归判断每个路径的黑节点个数是否相同 以及路径中是否存在两个连续红节点
		return _IsValidRBTree(root->_left, k, blackCount) && _IsValidRBTree(root->_right, k, blackCount);
	}

	// 判断是否为真的红黑树 【先得是二叉搜索树,然后再判断是不是红黑树】
	bool IsValidRBTree()
	{
		// 空树也是红黑树
		if (_root == nullptr)
			return true;

		// 不是空树判断该树是否符合红黑树的性质
		if (_root->_col == RED)
		{
			cout << "违反红黑树规则:根节点必须为黑\n";
			return false;
		}
        
		// 接下来验证每个路径的黑色节点个数都是一样的
		// 先随机获取一条路径的黑节点个数
		Node* cur = _root;
		size_t blackCount = 0;
		while (cur)
		{
			if (cur->_col == BLACK)
				blackCount++;

			cur = cur->_left; // 获取最左路径的黑节点个数
		}

		// 拿到了一条路径的黑色节点,验证每条路径的黑节点是否都是一样的、
		size_t k = 0; // 用k记录每个路径的黑节点个数
		return _IsValidRBTree(_root, k, blackCount); // 这里面还会判断是否存在连续2个红节点存在
	}

验证代码:

void TestRBTree()
{
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int b[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

	RedBlackTree<int, int> Rbtree;

	for (auto& e : a)
	{
		Rbtree.Insert(make_pair(e, e));
		//Rbtree.Insert(pair<int, int>(e, e)); // 等价于上面
	}

	Rbtree.InOrder();

	bool ret = Rbtree.IsValidRBTree();
	if (ret == 1)
	{
		cout << "该树是红黑树\n";
	}

	RedBlackTree<int, int> Rbtree2;
	for (auto& e : b)
	{
		Rbtree2.Insert(make_pair(e, e));
	}

	Rbtree2.InOrder();

	bool ret2 = Rbtree.IsValidRBTree();
	if (ret2 == 1)
	{
		cout << "该树是红黑树\n";
	}
}

验证后的结果:

image-20241013180823465

8.红黑树的应用

红黑树的应用非常的广泛

  1. C++ STL库-- map/set、mutil_map/mutil_set

  2. Java 库

  3. linux内核

  4. 其他一些库

9.红黑树和AVL树的效率对比

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

我这里的思路是生成N个随机数,然后放到一个vector中,然后通过范围for循环,取出vector的随机数,插入红黑树和AVLtree里面,看看插入的时间耗费了多少。

代码如下:

// 测试一下AVLTree和RedBlackTree的效率
void Compare()
{
	// 分别向两种树中插入10000个数字,看看效率
	int N = 1000000;
	vector<int> v;
	v.reserve(N); // 先开辟N个空间

	srand((unsigned int)time(nullptr)); //  给个种子

	// 先把随机数塞到这个vector中,插入树的时候从vector取
	for (int i = 0; i < N; i++)
	{
		v.push_back(rand());
	}

	RedBlackTree<int, int> Rbtree;
	AVLTree<int, int> Avltree;

	// 统计插入N个数 AVLTree需要多少时间
	int begin1 = clock();
	for (auto e : v)
	{
		Avltree.Insert(make_pair(e, e));

	}
	int end1 = clock();
	
	cout << "Avltree是否为正确的AVLTree:" << Avltree.IsBanlance() << endl;
	
	// 统计插入N个数 RedBlackTree需要多少时间
	int begin2 = clock();
	for (auto e : v)
	{
		Rbtree.Insert(make_pair(e, e));
	}
	int end2 = clock();

	cout << "Rbtree是否为正确的RedBlackTree:" << Rbtree.IsValidRBTree() << endl;


	cout << "AVLTree耗费的时间:" << end1 - begin1 << "ms" << endl;
	cout << "RedBlackTree耗费的时间:" << end2 - begin2 << "ms" << endl;
}

当N = 100000时

image-20241013155308154

当N = 1000000时

image-20241013155249070

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值