【C++】 红黑树(更新中)

本文详细介绍了红黑树的性质和优势,对比了它与AVL树的区别。红黑树通过特定的规则保持近乎平衡,从而在插入和删除操作时保持较高的效率。文章重点阐述了红黑树节点的插入过程,包括四种情况的处理和相应的旋转调整,展示了红黑树如何通过调整保持平衡并优化查找效率。
摘要由CSDN通过智能技术生成

前言

上篇博客学习了平衡二叉搜索树(AVLTree),了解到AVL树的性质,二叉搜索树因为其独特的结构,查找、插入和删除在平均和最坏情况下都是O(logn)。AVL树的效率就是高在这个地方。
但是在AVL树中插入或者删除结点,使得高度差的绝对值大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵平衡二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理,但是因为每个结点的高度差的绝对值都要小于1,这个条件较为的严格,所以导致多数情况的插入和删除都需要旋转调整,导致插入和删除的效率降低。
这时红黑树应运而生,并且因为其接近平衡的结构,使其查找效率也很高效,同时因为没有AVL树那样的严格要求,所以其插入和删除效率有时还高于AVL树,使其综合性能高于AVL树,所以红黑树的应用十分的广泛,比如在 Java 的集合框架 (HashMap、TreeMap、TreeSet),C++ 的 STL中都有以红黑树为底层结构实现的容器
那红黑树到底是什么呢?又是如何实现的呢?

在这里插入图片描述

一. 什么是红黑树

首先,红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。通过对任何一条从根到叶子的路径上各个结点着色方式的限制红黑树确保没有一条路径会是其他路径的两倍,因而是接近平衡
在这里插入图片描述

AVL树是依靠每个结点的左右高度差小于1来完成整棵树的平衡,而不会出现歪脖子树的场景
但是这样的要求较为的严格,导致插入和删除效率降低,而红黑树保持接近平衡是依靠以下几个规则:

  1. 每个结点不是红色就是黑色
  2. 根结点是黑色的
  3. 如果一个结点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该节点到其后代叶子结点的简单路径上,均包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点

从3规则我们可以推导出,红黑树中没有连续的红色结点
同时,红黑树中,最短路径最少是全是黑色结点的路径,而最长路径最多就是红黑相间的结点
而每个路径的黑色结点个数相同,最长路径最多是黑红相间,所以最长路径最多是 黑+红=2黑

二. 红黑树的效率

红黑树的最短路径:全黑
红黑树的最长路径:一黑一红,红黑相间

假设总共有N个结点
那么最短路径就是以2为底的logN
最长路径就是2logN
所以红黑树的效率最差就是2logN
而AVL树的查找效率是logN,二者的效率仅是2倍
因为以2为底的logN已经是不大的数了,所以2倍差距并不大。
但是红黑树的调整没有AVL树频繁,所以综合效率红黑树更胜一筹

在这里插入图片描述
像这样一棵树,如果是AVL树,则右边高度比左边高2,需要旋转,但是符合红黑树的条件,不用旋转

三. 红黑树的构建

(1). 结点结构体

红黑树结点的结构体大致与AVL树相同,但不需要平衡因子,而需要一个标记颜色的存储位
我们可以使用枚举体定义这个颜色的存储位。
代码如下:

//颜色的枚举体
enum Colour
{
	RED,
	BLACK,
};

//三叉链
//结点的结构体
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>*_left;//左指针
	RBTreeNode<K, V>*_right;//右指针
	RBTreeNode<K, V>*_parent;//双亲指针
	Colour _col;//颜色标记位
	pair<K, V>_kv;//KV值

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

//红黑树的类
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V>Node;
private:
	//根节点
	Node*_root = nullptr;
};

(2). 结点的插入

结点的插入,首先我们面临的一个问题是,默认插入的结点为什么是红色的呢?
新插入的结点是红色还是黑色,本质是要违反规则3,还是规则4
如果新插入的结点为黑色的话,一定改变路径上的黑色结点的个数,因为一次只能插入一个结点;但是如果插入红色结点,如果父亲节点是黑色,那插入就没有问题,如果为红色,那也只是影响最多两条路径,所以新增结点默认为红色。

而红黑树也是二叉搜索树,所以最开始时,结点的插入和二叉搜索树一样,都是通过kv值找到应插入的位置
在这里插入图片描述


那么接下来,我们就来分析结点插入的几种可能

1. 情况一

在这里插入图片描述
cur是新增结点,因为parent是黑色,所以插入结束


2. 情况二

在这里插入图片描述

结点15是新增结点,但是parent是红色结点,违反了规则3,出现了连续的红色结点,所以我们需要调整,又因为规则4,每个路径的黑色结点的个数相同,所以我们可能会修改隔壁路径。所以我们将这几个结点命名一下
调整的方式是这样的:
为了同时满足3,4规则,我们要改变结点颜色的同时,路径上的黑色结点个数还要相同,那么其实就是一直改变颜色,并且往上更新,最后到达两个路径的公共结点,然后在公共结点更改颜色,则不会影响这两个路径。
具体方法:如果存在uncle结点,且uncle结点为红色,则将parent结点和unclude结点都变成黑色,然后让grandfather结点变成红色
在这里插入图片描述

然后因为grandfather当前为根结点,所以再将grandfather结点变成黑色

在这里插入图片描述

我们发现,改变后的红黑树依然满足5条规则

如果结点数更多一点呢?
比如这样一棵红黑树
在这里插入图片描述
28是新增的结点
我们同样使用上述方法调节颜色
在这里插入图片描述
可以看到以25为根结点的子树完成了调整
但是这只是完成了一颗子树,所以还需要继续向上调整
所以我们让 cur=grandfather ,parent=cur->parent
在这里插入图片描述
这时parent有三种情况:

  1. 为整棵树的根结点。调整结束
  2. parent结点为黑色。调整结束
  3. parent结点为红色,继续调整

此处parent结点是红色,所以我们还需继续调整
在这里插入图片描述
最后因为根节点需要是黑色的,我们再将grandfather变成黑色

在这里插入图片描述
这样就完成了调整

这就是情况二:

插入结点的父亲结点是红色的,uncle结点存在且为红
则将parent和uncle都变黑grandfather变红,同时还需要继续向上更新


3. 情况三

在这里插入图片描述

当我们在6的右边新增结点
此时没有uncle结点,不满足情况二。

我们将grandfather这棵子树拎出来
在这里插入图片描述
我们会发现,这棵树的高度很不均衡,我们可以采用旋转的方式,降低这棵树的高度
图中这棵树的右边高度较高,所以我们要采用左单旋的方式

先将grandfather -> right = parent -> left,将parent的左结点给grandfather的右
再 parent -> left = grandfather,将grandfather给parent的左

在这里插入图片描述
这样子,这棵子树的高度就被我们降低了,并且没有破坏二叉搜索树的结构
但是还不满足红黑树,所以我们还需要调色
将parent变黑,grandfather变红
在这里插入图片描述

然后整棵树也就符合规则
在这里插入图片描述


单旋即后续的双旋,更详细的讲解可以阅读【C++STL】AVL树
简单来说
当出现这样的情况时,线性使用单旋
在这里插入图片描述
前者左边较高,使用右单旋平衡高度
后着右边较高,使用左单旋平衡高度

出现以下情况,折线型使用双旋
在这里插入图片描述

前者,左右双旋:先以拐点(红点)为轴点,先进行左单旋,变成左边高,再以黑点为轴点进行右单旋
后者,右左双旋:先以拐点(红点)为轴点,先进行右单旋,变成右边高,再以黑点为轴点进行左单旋
在这里插入图片描述
这里只作大致讲解,详细讲解可以阅读上面提到的博客


红黑树右单旋的情况和左单旋类似,读者可自己先尝试一下
在这里插入图片描述

旋转过程如下:
在这里插入图片描述

然后再调色
在这里插入图片描述


4. 情况四

情况二是uncle结点是红色,情况三是不存在uncle结点
情况四则是uncle结点为黑色
首先,什么时候会出现uncle结点为黑色
比如这样一棵红黑树
在这里插入图片描述
我们在4的左结点新增结点
那么首先看到parent和uncle都是红色,这是情况二
我们将parent和uncle变成黑色,grandfather变成红色
在这里插入图片描述

grandfather这棵子树完成调色,但是还需要继续向上调色
在这里插入图片描述

这时发现parent是黑色,插入成功,本次插入就结束了
我们在再在11的右子树新增结点
在这里插入图片描述

同样调整颜色,然后继续向上调整
在这里插入图片描述

而此时我们发现parent是红色,但是uncle却是黑色。
因为在之前的插入,uncle位置从红色变成了黑色,而parent在之后的一次插入中是grandfather,变成了红色
导致两个兄弟节点的颜色不同。
并且此时处于折线型
在这里插入图片描述
则我们需要使用双旋

这就是我们的情况四:parent为红,uncle存在且为黑色


双旋的过程如下:
在这里插入图片描述
我们先以parent为轴点,进行左单旋
在这里插入图片描述
先将parent->right=cur->left; 将cur的左给parent的右
在这里插入图片描述

再将cur->left=parent; 将parent变成cur的左

第一步旋转变成这样
在这里插入图片描述

第二步旋转,再以grandfather为轴点,进行一次右单旋
在这里插入图片描述
先将grandfather->left=cur->right;将cur的右给grandfather的左

在这里插入图片描述
再将cur->right=grandfather;将grandfather变成cur的右

最后将cur变成黑色,grandfather变成红色
在这里插入图片描述
这样,红黑树的左右双旋就完成了

5. 小总结

上述的四种情况,对应的是代码中的分类条件及旋转后的调色方法
而单旋或双旋的使用情况,是依照树的结构:线性,则使用单旋折线型,则使用双旋

6. 代码实现

	//插入
	bool Insert(const pair<K, V>&kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			//根节点是黑色的
			_root->_col = BLACK;
			return true;
		}

		Node*cur = _root;
		Node*parent = nullptr;
		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);
		//链接
		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 (grandfather->_left == parent)
			{
				Node*uncle = grandfather->_right;
				//uncle结点存在且为红
				//调色,并继续向上调整
				if (uncle&&uncle->_col == RED)
				{
					//调色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上调色
					cur = grandfather;
					parent = cur->_parent;
				}
				else//unlce不存在,或者uncle存在且为黑
				{

					//     g
					//   p
					// c
					//右单旋
					if (cur == parent->_left)
					{
						//以parent为轴点,进行右单旋
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
					else
					{
						//     g
						//   p   u
						//     c
						//左右双旋
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col=RED;
					}

					break;
				}
			}
			else  //parent是grandfather的右边
			{
				Node*uncle = grandfather->_left;
				//uncle结点存在且为红
				//调色,并继续向上调整
				if (uncle&&uncle->_col == RED)
				{
					//调色
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上调色
					cur = grandfather;
					parent = cur->_parent;
				}
				else//unlce不存在,或者uncle存在且为黑
				{

					//     g
					//   u   p
					//         c
					//左单旋
					if (cur == parent->_right)
					{
						//以parent为轴点,进行左单旋
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
						break;
					}
					else
					{
						//     g
						//   u   p
						//     c
						//右左双旋
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

				}
			}
		}

		//将根的颜色变成黑色
		_root->_col = BLACK;
	}


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

		//1. 将subRL变成parent的右节点
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;


		//记录当前子树的父节点
		Node*ppnode = parent->_parent;

		//2. 将parent变成subR的左节点
		subR->_left = parent;
		parent->_parent = subR;

		//3. 链接ppnode
		if (ppnode == nullptr)
		{
			//如果是ppnode是空,代表parent是根节点

			//更新根
			_root = subR;
			_root->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}
			subR->_parent = ppnode;
		}

	}


	//右单旋
	void RotateR(Node*parent)
	{
		Node*subL = parent->_left;
		Node*subLR = subL->_right;
		//1.将subLR变成parent的左节点
		parent->_left = subLR;
		//subLR可能是NULL,不是NULL才链接
		if (subLR)
			subLR->_parent = parent;

		//2.再将parent变成subL的右节点
		Node*ppnode = parent->_parent;//因为parent不一定是根节点,所以需要记录爷爷节点
		subL->_right = parent;
		parent->_parent = subL;

		//3.链接parent指针
		if (ppnode == nullptr)
		{
			//如果是根节点
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			//反之不是
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}
			subL->_parent = ppnode;
		}

	}

(3). 查找

红黑树的查找本质就是二叉搜索树的查找,根据二叉搜索树的性质:右子树的所有结点都比当前结点的值小,左子树的所有结点都比当前结点的值大
这样我们使用循环,比较大小,就可以决定接下来要去往左子树遍历还是右子树遍历
代码如下:

//查找
	Node* Find(const K&key)
	{
		assert(_root);

		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;
	}

(4). 红黑树的销毁

销毁我们可以使用析构函数,在当前函数结束时,就自动销毁红黑树,而析构函数内部其实调用一个后续遍历依次释放每个节点
代码如下:

//析构函数
	~RBTree()
	{
		_Destroy(_root);
		_root = nullptr;
	}

//销毁
	void _Destroy(Node*root)
	{
		//后续递归销毁
		if (root == nullptr)
			return;

		_Destroy(root->_left);
		_Destroy(root->_right);
		free(root);
	}

(5). 测试

红黑树的测试可以分两步
第一步检查是否满足二叉搜索树的性质:我们使用中序遍历验证
第二步根据红黑树的这三个性质:1. 根结点是黑色的 2. 每个路径的黑色结点个数相同 3. 不存在连续的红色结点 检查当前红黑树是否异常

中序遍历的代码如下:

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

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

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

中序遍历较为简单,这里不作解释


检查红黑树性质代码如下:

	//检查是否满足红黑树
	bool IsBalance()
	{
		//检查黑色结点的个数
		//最左路径的黑色结点个数
		int benchMark = 0;

		Node*cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				benchMark++;

			cur = cur->_left;
		}

		return _Check(_root,0,benchMark);
	}


	//递归检查
	bool _Check(Node*root,int blackNum,int benchMark)
	{
		//检查三个条件
		//1.根结点是否是黑色
		//2.是否有连续的红色结点
		//3.是否每条路径的黑色结点个数相同

		if (root == nullptr)
		{
			if (blackNum == benchMark)
				return true;
			else
			{
				cout << "黑色结点个数异常" << endl;
				return false;
			}
		}

		//1.根结点如果是红色的,代表编写异常
		if (root == _root && root->_col == RED)
		{
			cout << "根结点颜色异常" << endl;
			return false;
		}

		//2.如果当前结点是红色的
		//则需要检查其父亲结点是否是红色
		if (root->_col == RED && root->_parent->_col == RED)
		{
			cout << "出现连续的红色结点" << endl;
			return false;
		}

		//如果是黑色结点,则要记录
		if (root->_col == BLACK)
		{
			blackNum++;
		}

		return _Check(root->_left, blackNum, benchMark) && 
		_Check(root->_right, blackNum, benchMark);
	}
  1. 如果根结点是红色的,异常
  2. 如果当前结点是红色的,并且父亲结点也是红色,即出现连续的红色结点,异常
    检查父亲的原因:因为一个结点,一定有父亲结点,但是不一定有孩子结点
  3. blackNum记录黑色结点的个数,我们在检查前,先遍历最左路径,计算出黑色结点个数,然后拿着这个值去每一条路径比较,如果有不一样的,要么最左路径出问题,要么该路径出问题

四. 迭代器

红黑树因其特殊结构和高效查找,插入,删除效率而应用广泛。C++STL中的map和set的底层就是使用的红黑树。所以我们要为红黑树提供访问的各种接口,迭代器就是其中最为重要的访问方式


迭代器本质就是结点的指针,但是是将结点指针和方法结合起来的结构体。
但是因为红黑树和线性表并不同,并不是连续的存储结构,结点之间是依靠指针连接的,所以迭代器的实现并不容易。那么接下来,我们就来一点点设计红黑树的迭代器

(1). 迭代器的结构体

首先,迭代器需要应用于抽象数据,所以我们需要使用模板,同时迭代器是一个结构体,所以我们需要重载一些运算符,来实现其效果
以下是迭代器结构体的代码:

//迭代器
template<class T,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _RBTreeIterator<T, Ref, Ptr> Self;
public:
	//构造函数
	_RBTreeIterator(const Node*node)
		:_node(node)
	{}
private:
	Node*_node;
};

在这里插入图片描述

模板参数:
class T:红黑树结点的数据类型
class Ref:返回迭代器内部指针解引用的成员变量类型的引用
适配*运算符的重载
class Ptr:返回迭代器内部的指针
适配->运算符的重载

(2). 迭代器的访问

我们预期的迭代器的使用方式是这样的:我们拿set作例子

void test_set1()
	{
		set<int> s;
		s.Insert(1);
		s.Insert(2);
		s.Insert(3);

		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		
	}

需要可以一个结点一个结点的遍历set中的元素,因为底层红黑树是树结构,中序遍历是一次性全访问完,而迭代器实现的就是像线性结构那样,一个结点一个结点的访问。

set和map的大部分功能实现,其实都是直接使用红黑树的功能


begin()
首先使用迭代器遍历,我们需要确定红黑树遍历的起始位置,因为中序遍历出来的是升序,所以我们第一个访问的结点就是最小结点。而最小结点在哪呢?其实红黑树的最左结点就是最小结点,所以我们只需要遍历到最左结点就好了。代码如下:

	//返回最左结点
	iterator begin()
	{
		Node*cur = _root;
		//可能是空树,所以要判断一下
		while (cur&&cur->_left)
			cur = cur->_left;

		return iterator(cur);
	}

需要注意的点是当前树可能是空树,所以不能一开始就解引用,可能出现空指针解引用的情况
begin()返回的位置如下
在这里插入图片描述


end()
end直接返回空就好了,因为各结点的链接关系都完整,遍历结束后,会访问到最右结点的下一个结点,也就是空结点

	iterator end()
	{
		return iterator(nullptr);
	}

接下来是几个运算符重载,因为迭代器不是内置类型,所以对于迭代器的!=*++,编译器都是无法识别的

运算符重载

!=运算符重载
因为迭代器内部就一个指针类型,所以判断是否相同,其实就是用指针比较
代码如下:

	//判断
	//Self是重命名的迭代器类型
	bool operator!=(const Self&s)
	{
		return _node != s._node;
	}

*运算符重载
因为*的作用是解引用,返回存储的数据,并且支持修改,所以我们需要使用引用返回
代码如下:

	//返回解引用的数据
	//Ref是T&或者const T&
	Ref operator*()
	{
		return _node->_data;
	}

++运算符重载
在这里插入图片描述
比如这样一棵树,begin()返回的位置如图,我们要使用++来遍历这棵树,分为以下几种情况

  1. 当前结点的右结点不为空,如果接下来要访问的就是右子树的最左节点
  2. 右结点为空,再分两类

(1). 如果当前访问的结点是父亲结点的右结点,那么父亲结点就一定访问过了,我们访问父亲结点的父亲结点,也就是祖父结点,同时也需要判断父亲结点是否是祖父结点的右结点
(2). 如果当前访问的结点是父亲结点的左结点,那么父亲结点就是下一个访问的结点

代码如下:

	//迭代器++
	Self&operator++()
	{
		//右不为空,下一个就是右子树的最左结点
		if (_node->_right)
		{
			Node*subLeft = _node->_right;
			while (subLeft->_left)
				subLeft = subLeft->_left;

			_node=subLeft;
		}
		else
		{
			//右为空,向上找,找孩子是父亲结点的左孩子
			Node*parent = _node->_parent;
			while (parent&&parent->_right == _node)
			{
				_node = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

--运算符

--运算符的大逻辑跟++运算符相同,只不过逻辑条件不一样

  1. 如果当前结点的左结点不为空,则下一个访问的是左结点的最右结点
  2. 如果当前结点的左结点为空,则同样分为两类

(1). 结点是父亲结点的左结点,那么父亲结点已经访问过了
(2). 结点是父亲结点的右结点,那么父亲结点就是下一个访问的结点

代码如下:

	//迭代器--
	Self&operator--()
	{
		//左结点不为空,找左结点的最右结点
		if (_node->_left)
		{
			Node*subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			//左结点为空,向上找结点是其父亲结点的右孩子
			Node*cur = _node;
			Node*parent = cur->_parent;
			//找结点是其父亲结点的右孩子
			while (parent&&cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

结束语

本篇博客内容暂时到此,红黑树结点的删除,较为复杂,后续再继续补充

本篇内容到此就结束了,感谢你的阅读!

如果有补充或者纠正的地方,欢迎评论区补充,纠错。如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,我是一名语言模型AI,无法为您提供演示实时代码功能。但是,我可以向您解释红黑树的基本原理和操作。红黑树是一种自平衡二叉搜索树,它的每个节点都有一个颜色属性,红色或黑色,具有以下特性: 1. 根节点为黑色。 2. 所有叶子节点(NIL节点)为黑色。 3. 如果一个节点为红色,则它的两个子节点必须都为黑色。 4. 从任意一个节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。 红黑树的基本操作包括插入、删除和查找。插入和删除操作会破坏红黑树的平衡性,需要通过旋转和重新着色来修复平衡。查找操作与二叉搜索树相同,具有O(log n)的时间复杂度。 以下是红黑树的插入操作的伪代码: 1. 新节点插入到红黑树,颜色为红色。 2. 如果新节点的父节点为黑色,则不需要做任何操作,树仍然平衡。 3. 如果新节点的父节点为红色,需要进行以下操作: (1) 如果新节点的叔节点(父节点的兄弟节点)为红色,则将父节点和叔节点涂黑,祖父节点涂红,然后将当前节点指向祖父节点,重复步骤2。 (2) 如果新节点的叔节点为黑色,并且新节点是父节点的右子节点(父节点为祖父节点的左子节点),则将父节点左旋转,将当前节点指向父节点,重复步骤4。 (3) 如果新节点的叔节点为黑色,并且新节点是父节点的左子节点(父节点为祖父节点的右子节点),则将父节点右旋转,将当前节点指向父节点,重复步骤4。 4. 将父节点涂黑,祖父节点涂红,然后进行以下操作: (1) 如果当前节点是父节点的左子节点,将祖父节点右旋转。 (2) 如果当前节点是父节点的右子节点,将祖父节点左旋转。 以上是红黑树的基本操作,希望能够帮助您理解红黑树的原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值