C++ STL篇 红黑树的模拟实现

前言

在网络上流传着这样一张图片:

这张图片表达的侧面意思是:红黑树非常难!!!但如果认真阅读了这篇的博客,并且你有 AVL 树的基础的话 (重点是 AVL 树的旋转),其实你会发现,红黑树难只是指红黑树比较抽象,但它的逻辑其实是比 AVL 树要简单的,并且红黑树的代码也不难写。

一、红黑树的概念

1.1、什么是红黑树

红黑树首先是一棵二叉搜索树,相比AVL树他的每个结点增加一个存储位来表示结点的颜色而不是平衡因子,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的。

1.2、红黑树的原则

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


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

1.3、关于"路径"的理解(高频易错点)

在树结构中,"路径"的定义非常容易被误解。很多人会认为:路径数 = 叶子结点个数。例如下图中有 4 个叶子结点,于是误以为有 4 条路径------这是错误的。

正确理解是:路径必须延伸到"空结点(NULL)"。

也就是说,每一条从根节点出发,最终走到一个空指针的位置,才算一条完整路径。

因此,路径的数量实际上等于空子树(NULL指针)的数量。在该例中,共有 9 个空结点,所以路径数应为 9 条,而不是 4 条。

以上图为例,路径示意图:

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

由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径就是全是黑色结点的路径,假设最短路径长度为bh(就是black height)。

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

好!我们再结合红黑树的4点规则,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在的。假设任意一条从根到NULL结点路径的长度为x,那么bh <= h <= 2* bh。

这就是---------红黑树确保最长路径不超过最短路径2倍的秘诀。

1.5、红黑树的效率

设N是红黑树树中结点数量,h是最短路径的长度,那么我们就能得到公式:

由此推出 h≈logN,也就是说意味着红黑树增删查改最坏也就是走最长路径2∗logN那么时间复杂度还是 O(log⁡N)。

红黑树的表达相对AVL树要抽象一些,AVL树通过高度差直观地控制了平衡。红黑树通过4条规则的颜色约束,间接地实现了近似平衡,它们效率都是同一档次,但是相对而言,插入相同数量的结点,红黑树的旋转次数是更少的,因为他对平衡的控制没那么严格。

二、红黑树的节点结构

红黑树的节点结构和 AVL 树整体上类 似,三个节点指针分别指向左孩子、右孩子和父亲,然后还包含一个 pair 键值对,和 AVL 树不同的时,红黑树不再需要 bf 变量来作为树的平衡因子,而是需要一个 col 变量来标识节点的颜色:

cpp 复制代码
	enum Color//颜色枚举
	{
		RED,
		BLACK
	};
	template<class K,class V>
	struct RBTreeNode
	{
		RBTreeNode(pair<K,V> kv=)
			:_left(nullptr)
			, _right(nullptr)
			,_parent(nullptr)
			,_kv(kv)
			,_color(RED)//默认给成红色
		{
		}
		~RBTreeNode();

		RBTreeNode<K, V>* _left;
		RBTreeNode<K, V>* _right;
		RBTreeNode<K, V>* _parent;
		pair<K,V> _kv;
		Color _color;//节点颜色
	}; 
	template<class K, class V>
	class RBTree
	{
		typedef RBTreeNode<K, V> Node;
	public:
		RBTree()
			:_root(nullptr)
		{
		}
		~RBTree();

	private:
		Node* _root;
	};

2.1、节点的默认颜色

可以看到,在构造函数 的初始化列表中,我们将节点的颜色默认初始化为 RED,这是因为新增节点的颜色默认给成红色更合适,原因如下:

  • 如果新增节点的颜色为红色,那么这里存在三种情况:
  1. 一是新增节点为根节点,此时我们需要将节点颜色改为黑色;
  2. 二是新增节点的父节点颜色为红色,由于红黑树中不能出现连续的红色节点,所以我们需要进行调整;
  3. 三是新增节点的父节点颜色为黑色,此时我们不需要做任何事,因为新增节点没有违反红黑树的性质。
  • 而如果新增节点的颜色为黑色,那我们一定需要对该节点进行调整,因为新增节点会导致这条路径的黑色节点数量比其他所有路径的黑色节点数量多一,违反了红黑树的性质4。(调整是多条路的)

综合考量上面两种情况,我们认为将新增节点颜色默认设置为红色更合适 (黑色必调整且很难调整,红色可能调整且调整较易)。

三、红黑树的插入

3.1、红黑树前置插入

红黑树插入的前面部分很简单,就是一般二叉搜索树的插入逻辑,需要注意的是如果插入的节点是根节点,则我们需要将节点的颜色改为黑色,因为新增节点默认是被初始化 为红色的;

红黑树插入的难点在于检测插入后红黑树的性质是否被破坏,其一共可以分为两种情况:

  1. 父节点的颜色为黑色,此时没有违反红黑树任何性质,不需要调整;
  2. 父节点的颜色为红色,此时出现了连续红色节点,需要进行调整。
cpp 复制代码
		bool insert(pair<K, V> kv)
		{
			Node* newnode = new Node(kv);
			
			//空树
			//变成根加变色
			if (_root == nullptr)
			{
				_root = newnode;
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;//相等不处理
				}
			}
			//cur就是要插入位置
			cur = newnode;
			
			//链接到父节点
			if (cur->_kv.first < parent->_kv.first)
				parent->_left = cur;
			else if (cur->_kv.first > parent->_kv.first)
				parent->_right = cur;
			else
				return false;

			//统一修改父节点
			cur->_parent = parent;
		}

3.2、分析:

C是红色:为了让错误必然发生------p为红色。

C是红色,p为红色:为了让插入前红黑树成立------g必为黑。

这三个颜色都固定了,关键的变化看 u 的情况,需要根据u分为以下几种情况分别处理。 接下来就是分情况讨论啦。

而红黑树的调整又分为两种情况:(约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点)

四、红黑树的调整

4.1、叔叔存在且为红------直接变色

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

4.1.1、c是新增结点

分析:

因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加一个黑色结点,g再变红,相当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题.

需要继续往上更新是因为:

  1. 如果g的父亲还是红色,那么就还需要继续处理。
  2. 如果g的父亲是黑色,则处理结束了。
  3. 如果g就是整棵树的根,再把g变回黑色。

这种情况只变色,不旋转。所以无论c是p的左还是右,p是g的左还是右,都是上面的变色处理方式。

4.1.2、c不是新增结点的抽象分析

以上我们仅讨论了插入节点后的一种情况。当节点 10 被染为红色后,如果其父节点仍然是红色,则需要继续向上调整,如图所示。此时,上文中的节点 10 就对应下图中的节点 x 。

为了更一般化地描述所有可能的情形,我们将子树进行抽象处理:

  • 子树 a 和 b 是黑色节点高度为 bh−1的任意子树;
  • 子树 d、e 、 f 是黑色节点高度为 bh的任意子树。

对这种情况仍执行相同的变色操作:将父节点 p和叔节点 u 染黑,祖父节点g 染红。随后,将 g 视为新的"当前节点" c ,继续向上进行调整。这一过程可递归重复,直到满足红黑树的性质为止。

4.1.3、c不是新增结点的具体分析

  • 情况一:hb == 0

a/b/d/e/f都是空,c为新增节点。需要注意的是,x是6和15节点的任意一个孩子,都会引发这里的变色逻辑。

  • 情况二:hb == 1

子树可能出现的情况:

hb == 1,d/e/f为hb==1的红黑树:

c之前是黑色节点,在a和b中插入引发c变色为红色

d/e/f为x/y/z/m中任意一种,组合为444

a和b为红色节点,在a和b的四个孩子的任意位置插入,都会让a和b变成黑色,c变成红色,继续往上更新,插入位置有4个位置。

所有情况组合起来合计:444*4 = 256

  • 情况二:hb==2

可能出现的子树情况:

hb == 2,d/e/f为hb= =2的红黑树,a和b是hb= =1的根为红色的树

d/e/f的组合为:(256+16)(256+16)(256+16) = 20123648

a和b为根节点为红色节点的hb==1的树,这里可以看到a和b插入组合也不少

a或者b插入至少要经历两次变色和向上处理才能得到这里的情况,这里的组合情况至少是百亿以上了。

4.2、叔叔不存在或存在且为黑+直线结构------变色+单旋

4.2.1、c是新增结点

方法:

将p结点变黑,将g结点变红。

以g结点为旋转轴发生一次右单旋。

分析:当前问题是 c 和 p 同为红色,违反了红黑树"不能有连续红结点"的性质,因此必须让 p 变黑,从而直接消除这一对红红冲突。同时将 g 变红,是为了在旋转后维持整棵子树的黑高不变,否则会导致从 g 出发的不同路径黑色结点数量不一致。

是否需要继续向上? 不需要:旋转完成后,p 作为新的子树根节点为黑色,既消除了当前的红红冲突,又不会与其父节点产生新的红红冲突,同时整棵子树的黑高保持不变,因此无需继续向上调整。

  • 不可能出现的情况

c为红,p为红,g为黑,u不存在或者u存在且为黑,先说结论:

u不存在,则c一定是新增结点。

u存在且为黑,则c一定不是新增。

  • 为什么u不存在的时候,c一定是新增结点?

如图,假如c不是新增结点,那么3结点在最初的时候是黑色的,(3结点是在下面那一轮中作为爷爷结点在变色过程中变红的)

于是10->6->3路径下就会有2个黑色结点,而10->NIL路径下只有一个黑色结点,破坏了规则四。

  • 为什么u存在且为黑的时候,c一定不是新增结点?

    和上面一样的道理,如果u存在且为黑,如果c是新插入的结点,那么10->6->3路径上只有一个黑色结点,10->15路径上就会有两个黑色结点,破坏了规则四。

5.2.2、c不是新增结点的情况

方法:

将p结点变黑,将g结点变红。

以g结点为旋转轴发生一次右单旋。

分析:此时由于叔节点为黑,无法通过再次变色维持黑高平衡,因此必须通过旋转重构局部结构。单旋通过提升父节点、降低祖父节点,实现黑高度的重新分配,并彻底消除红红冲突,同时保证调整不会继续向上传播。

4.3、叔叔不存在或存在且为黑+折线结构------变色+双旋

4.3.1、c是新插入结点

条件:

c为红,p为红,g为黑,u不存在或者u存在且为黑,此时结构呈"折线型",单旋无法解决问题,需要进行双旋调整。

方法:

先以 p 为旋转轴进行一次单旋(将结构拉直),再以 g 为旋转轴进行一次单旋,最后将 c 变黑,g 变红。

分析: 由于当前结构为折线,若直接以 g 进行单旋,无法同时满足"消除红红冲突"和"保持黑高不变"两个条件。第一次围绕 p 的旋转,本质是将折线结构转换为直线结构,第二次围绕 g 的旋转,才是真正完成局部结构的重构。

将 c 变黑,这样左右黑色高度控制到插入前状态;将 g 变红,是为了保证调整前后子树右侧黑色结点数量一致。

是否需要继续向上? 不需要:子树已经局部完全平衡,且 c 作为新的根节点为黑色,不会与其父节点产生连续红色结点的问题。

4.3.2、c不是新插入结点

方法:

先以 p 为旋转轴进行一次单旋(将结构拉直),再以 g 为旋转轴进行一次单旋,最后将 c 变黑,g 变红。

c之前是黑色结点,是在其子树中插入后触发了情况一(u为红,注意是情况一),经过变色将其染为红色,并继续向上更新,最终在当前层形成红红冲突。

条件:此时仍满足:c为红,p为红,g为黑,u不存在或者u存在且为黑,且结构为折线型。

分析:由于叔节点为黑,无法通过再次变色维持黑高平衡,同时当前结构为折线,单旋无法完成结构调整,因此必须通过双旋来同时解决结构问题与颜色问题。第一次旋转将结构拉直,第二次旋转完成子树重构。

调整后将 c 变黑,g 变红,使 c 成为该子树新的根节点,从而保证黑色结点数量不变,并彻底消除连续红色结点的问题。

是否需要继续向上? 不需要,调整完成后子树已经满足红黑树所有性质,且新的根节点 c 为黑色,不会继续向上传播问题。

五、红黑树插入的代码

cpp 复制代码
		bool insert(pair<K, V> kv)
		{
			Node* newnode = new Node(kv);
			
			//空树
			//变成根加变色
			if (_root == nullptr)
			{
				_root = newnode;
				_root->_col = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;//相等不处理
				}
			}
			//cur就是要插入位置
			cur = newnode;
			
			//链接到父节点
			if (cur->_kv.first < parent->_kv.first)
				parent->_left = cur;
			else if (cur->_kv.first > parent->_kv.first)
				parent->_right = cur;
			else
				return false;

			//统一修改父节点
			cur->_parent = parent;
			
			//调整
			//核心:(解决红红冲突)
			//只要父亲还是红节点就要处理
			while (parent && parent->_color == RED)
			{
				//祖先节点
				Node* grandpa = parent->_parent;
				//叔叔节点
				Node* uncle = nullptr;
				//先找到叔叔
				//如果父节点在祖父节点的左边
				if (grandpa->_left == parent)	
				{
					uncle = grandpa->_right;
					//情况一:叔节点存在且为红(只变色)
					if (uncle && uncle->_color == RED)
					{
						grandpa->_color = RED;
						parent->_color = BLACK;
						uncle->_color = BLACK;
						//迭代继续走
						cur = grandpa;
						parent = cur->_parent;
					}
					//叔叔不存在或存在且为黑
					else if (uncle == nullptr || uncle->_color == BLACK)
					{
						//     g()(b)
						//   p(r)    u(b)
						// c(r)
						// L 型 → 单旋
						if (parent->_left == cur)
						{
							RotateR(grandpa);
						}
						//     g()(b)
						//   p(r)    u(b)
						//        c(r)
						// LR 型 → 左右双旋
						if (parent->_right == cur)
						{
							RotateLR(grandpa);
						}
						//颜色统一处理
						parent->_color = BLACK;
						grandpa->_color = RED;
						//旋转完以后子树已经符合红黑树的性质了
						break;
					}
				}

				else if (grandpa->_right== parent)
				{
					//父亲在右边,叔叔在左边
					uncle = grandpa->_left;
					//情况一:叔节点存在且为红(只变色)
					if (uncle && uncle->_color == RED)
					{
						grandpa->_color = RED;
						parent->_color = BLACK;
						uncle->_color = BLACK;
						//迭代继续走
						cur = grandpa;
						parent = cur->_parent;
					}
					//情况二:叔叔不存在或存在且为黑
					else if (uncle == nullptr || uncle->_color == BLACK)
					{
						//     g(b)
						//   u(b)  p(r)
						//             c(r)
						// L 型 → 单旋
						if (parent->_right == cur)
						{
							RotateL(grandpa);
						}
						//     g(b)
						//   u(b)  p(r)
						//       c(r)
						// RL 型 → 双旋
						else if (parent->_left == cur)
						{
							RotateRL(grandpa);
						}
						//颜色统一处理
						parent->_color = BLACK;
						grandpa->_color = RED;
						//旋转完以后子树已经符合红黑树的性质了
						break;
					}
				}		
			}
			//直接把根节点颜色置黑
			_root->_color = BLACK;
			return true;
		}

六、红黑树的验证

6.1、红黑树验证原理

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

  1. 规则1枚举颜色类型,天然实现保证了颜色不是黑色就是红色。
  2. 规则2直接检查根即可
  3. 规则3前序遍历检查,遇到红色结点孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲的颜色就方便多了
  4. 规则4前序遍历,遍历过程中用形参记录跟到当前结点的blackNum,前序遍历遇到黑色结点就++blackNum,走到空就计算出一条路径的黑色结点数量。再任意一条路径黑色结点数量作为参考值,依次比较即可。

6.2、红黑树验证代码

cpp 复制代码
		bool Check(Node* cur, int BlackNum, int BaseCount)
		{
			//如果走到空,说明这条路径结束了,此时看BlackNum与baseCount是否相等
			if (cur == nullptr)
			{
				if (BlackNum == BaseCount)
					return true;
				else
					return false;
			}

			//检查是否出现连续红色节点
			if (cur->_color == RED) 
			{
				//红色节点一定有父亲,因为根节点为黑色
				if (cur->_parent->_color == RED)
				{ 
					cout << "出现连续红色节点,节点key值:" << cur->_kv.first << endl;
				}
			}

			if (cur->_color == BLACK)
				BlackNum++;

			return Check(cur->_left, BlackNum, BaseCount)
				&& Check(cur->_right, BlackNum, BaseCount);
		}

		//判断
		bool IsBanlance()
		{
			//检查根节点
			if (_root->_color == RED)
			{
				cout << "根为红" << endl;
				return false;
			}

			//以最左路径黑色节点数量为基准,同其他路径的黑色节点数量相比较,看是否相等
			Node* cur = _root;
			int BaseCount = 0;
			while (cur)
			{
				if (cur->_color == BLACK)
					BaseCount++;

				cur = cur->_left;
			}

			//检查每一颗树是否满足规则
			return Check(_root, 0, BaseCount);
		}

七、其他接口代码

cpp 复制代码
		//个数
		int size(RBTree* RB)
		{
			return _size(RB->_root);
		}

		//高度
		int height(RBTree* RB)
		{
			return _height(RB->_root);
		}

		//中序遍历
		void inorder(RBTree* RB)
		{
			return _inorder(RB->_root);
		}
		//	查找函数
		Node* find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_kv.first)
					cur = cur->_left;
				else if (key > cur->_kv.first)
					cur = cur->_right;
				else
					return cur;
			}
			return nullptr;
		}
		int _size(Node* cur)
		{
			if (cur == nullptr)
				return 0;
			return _size(cur->_left) + _size(cur->_right) + 1;
		}
		int _height(Node* cur)
		{
			if (cur == nullptr)
				return 0;
			int heightL = _height(cur->_left) + 1;
			int heightR = _height(cur->_right) + 1;

			return heightL > heightR ? heightL : heightR;
		}
		void _inorder(Node* cur)
		{
			_inorder(cur->_left);
			cout << "K:" << cur->_kv.first << "V:" << cur->_kv.second << "   ";
			_inorder(cur->_right);
		}

八、红黑树完整代码加测试

cpp 复制代码
#pragma once
#include <iostream>

using namespace std;

namespace ljm
{
	enum Color//颜色枚举
	{
		RED,
		BLACK
	};

	template<class K,class V>
	struct RBTreeNode
	{
		RBTreeNode(pair<K,V> kv)
			:_left(nullptr)
			, _right(nullptr)
			,_parent(nullptr)
			,_kv(kv)
			,_color(RED)//默认给成红色
		{
		}
		~RBTreeNode();

		RBTreeNode<K, V>* _left;
		RBTreeNode<K, V>* _right;
		RBTreeNode<K, V>* _parent;
		pair<K, V> _kv;
		Color _color;//节点颜色
	}; 
	template<class K, class V>
	class RBTree
	{
		typedef RBTreeNode<K, V> Node;
	public:
		RBTree()
			:_root(nullptr)
		{
		}
		~RBTree()
		{
			// 调用后序遍历销毁所有节点
			destroy(_root);
			_root = nullptr;
		}
		// 后序遍历释放节点
		void destroy(Node* cur)
		{
			if (cur == nullptr)
				return;

			destroy(cur->_left);
			destroy(cur->_right);
			delete cur; // 最后删除自己
		}

		// 右单旋:以 cur 为旋转轴进行右旋
		void RotateR(Node* cur) 
		{
			//         c
			//    subL   ...
			// ...	subLR
			Node* subL = cur->_left;
			Node* subLR = subL->_right;
			//这个主要用来负责父节点指针相连的
			Node* pparent = cur->_parent;
			cur->_left = subLR;
			subL->_right = cur;
			if(subLR)
					subLR->_parent = cur;
			subL->_parent = pparent;
			cur->_parent = subL;
			if (_root==cur)
			{
				// cur 是根,旋转后 subL 变成新根
				_root = subL;
			}
			else
			{
				//父节点指针处理完成
				if (pparent->_left == cur)
					pparent->_left = subL;
				else if (pparent->_right == cur)
					pparent->_right = subL;
			}
		}
		// 左单旋:以 cur 为旋转轴进行左旋(RR 型)
		void RotateL(Node* cur) 
		{
			//			c
			// ...	         subR
			//			 subRL   ...
			Node* subR = cur->_right;
			Node* subRL = subR->_left;
			Node* pparent = cur->_parent;
			
			subR->_left = cur;
			cur->_right = subRL;

			if (subRL)
				subRL->_parent = cur;
			subR->_parent = pparent;
			cur->_parent = subR;
			
			if (_root == cur)
			{
				_root = subR;
			}
			else
			{
				if (pparent->_left == cur)
					pparent->_left = subR;
				else if (pparent->_right == cur)
					pparent->_right = subR;
			}
		}


		// 右左双旋:先右旋,再左旋(RL 型)
		void RotateRL(Node* cur) 
		{
			//			c
			//				subR
			//			subRL
			Node* subR = cur->_right;
			Node* subRL = subR->_left;

			RotateR(subR);
			RotateL(cur);
		}


		// 左右双旋:先左旋,再右旋(LR 型)
		void RotateLR(Node* cur)
		{
			Node* subL = cur->_left;
			Node* subLR = subL->_right;

			RotateL(subL);
			RotateR(subLR);
		}


		bool insert(pair<K, V> kv)
		{
			Node* newnode = new Node(kv);
			
			//空树
			//变成根加变色
			if (_root == nullptr)
			{
				_root = newnode;
				_root->_color = BLACK;
				return true;
			}

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (kv.first > cur->_kv.first)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (kv.first < cur->_kv.first)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;//相等不处理
				}
			}
			//cur就是要插入位置
			cur = newnode;
			
			//链接到父节点
			if (cur->_kv.first < parent->_kv.first)
				parent->_left = cur;
			else if (cur->_kv.first > parent->_kv.first)
				parent->_right = cur;
			else
				return false;

			//统一修改父节点
			cur->_parent = parent;
			
			//调整
			//核心:(解决红红冲突)
			//只要父亲还是红节点就要处理
			while (parent && parent->_color == RED)
			{
				//祖先节点
				Node* grandpa = parent->_parent;
				//叔叔节点
				Node* uncle = nullptr;
				//先找到叔叔
				//如果父节点在祖父节点的左边
				if (grandpa->_left == parent)	
				{
					uncle = grandpa->_right;
					//情况一:叔节点存在且为红(只变色)
					if (uncle && uncle->_color == RED)
					{
						grandpa->_color = RED;
						parent->_color = BLACK;
						uncle->_color = BLACK;
						//迭代继续走
						cur = grandpa;
						parent = cur->_parent;
					}
					//叔叔不存在或存在且为黑
					else if (uncle == nullptr || uncle->_color == BLACK)
					{
						//     g()(b)
						//   p(r)    u(b)
						// c(r)
						// L 型 → 单旋
						if (parent->_left == cur)
						{
							RotateR(grandpa);
						}
						//     g()(b)
						//   p(r)    u(b)
						//        c(r)
						// LR 型 → 左右双旋
						if (parent->_right == cur)
						{
							RotateLR(grandpa);
						}
						//颜色统一处理
						parent->_color = BLACK;
						grandpa->_color = RED;
						//旋转完以后子树已经符合红黑树的性质了
						break;
					}
				}

				else if (grandpa->_right== parent)
				{
					//父亲在右边,叔叔在左边
					uncle = grandpa->_left;
					//情况一:叔节点存在且为红(只变色)
					if (uncle && uncle->_color == RED)
					{
						grandpa->_color = RED;
						parent->_color = BLACK;
						uncle->_color = BLACK;
						//迭代继续走
						cur = grandpa;
						parent = cur->_parent;
					}
					//情况二:叔叔不存在或存在且为黑
					else if (uncle == nullptr || uncle->_color == BLACK)
					{
						//     g(b)
						//   u(b)  p(r)
						//             c(r)
						// L 型 → 单旋
						if (parent->_right == cur)
						{
							RotateL(grandpa);
						}
						//     g(b)
						//   u(b)  p(r)
						//       c(r)
						// RL 型 → 双旋
						else if (parent->_left == cur)
						{
							RotateRL(grandpa);
						}
						//颜色统一处理
						parent->_color = BLACK;
						grandpa->_color = RED;
						//旋转完以后子树已经符合红黑树的性质了
						break;
					}
				}		
			}
			//直接把根节点颜色置黑
			_root->_color = BLACK;
			return true;
		}

		//个数
		int size()
		{
			return _size(_root);
		}

		//高度
		int height()
		{
			return _height(_root);
		}

		//中序遍历
		void inorder()
		{
			return _inorder(_root);
		}
		//	查找函数
		Node* find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_kv.first)
					cur = cur->_left;
				else if (key > cur->_kv.first)
					cur = cur->_right;
				else
					return cur;
			}
			return nullptr;
		}

		bool Check(Node* cur, int BlackNum, int BaseCount)
		{
			//如果走到空,说明这条路径结束了,此时看BlackNum与baseCount是否相等
			if (cur == nullptr)
			{
				if (BlackNum == BaseCount)
					return true;
				else
					return false;
			}

			//检查是否出现连续红色节点
			if (cur->_color == RED) 
			{
				//红色节点一定有父亲,因为根节点为黑色
				if (cur->_parent->_color == RED)
				{ 
					cout << "出现连续红色节点,节点key值:" << cur->_kv.first << endl;
				}
			}

			if (cur->_color == BLACK)
				BlackNum++;

			return Check(cur->_left, BlackNum, BaseCount)
				&& Check(cur->_right, BlackNum, BaseCount);
		}

		//判断
		bool IsBanlance()
		{
			//检查根节点
			if (_root->_color == RED)
			{
				cout << "根为红" << endl;
				return false;
			}

			//以最左路径黑色节点数量为基准,同其他路径的黑色节点数量相比较,看是否相等
			Node* cur = _root;
			int BaseCount = 0;
			while (cur)
			{
				if (cur->_color == BLACK)
					BaseCount++;

				cur = cur->_left;
			}

			//检查每一颗树是否满足规则
			return Check(_root, 0, BaseCount);
		}
	private:
		int _size(Node* cur)
		{
			if (cur == nullptr)
				return 0;
			return _size(cur->_left) + _size(cur->_right) + 1;
		}
		int _height(Node* cur)
		{
			if (cur == nullptr)
				return 0;
			int heightL = _height(cur->_left) + 1;
			int heightR = _height(cur->_right) + 1;

			return heightL > heightR ? heightL : heightR;
		}
		void _inorder(Node* cur)
		{
			if (cur == nullptr)
				return;
			_inorder(cur->_left);
			cout << "K:" << cur->_kv.first << "V:" << cur->_kv.second << "   ";
			_inorder(cur->_right);
		}
		Node* _root;
	};
	void test()
	{
		RBTree<int, int> rb1;
		rb1.insert(make_pair(1, 5));
		rb1.insert(make_pair(2, 5));
		rb1.insert(make_pair(4, 5));
		rb1.insert(make_pair(3, 5));
		rb1.insert(make_pair(5, 5));
		rb1.insert(make_pair(2, 1));
		rb1.inorder();
		RBTreeNode<int,int>* tmp= rb1.find(2);
		cout << endl;
		cout << tmp->_kv.first << endl;
		cout << rb1.height() << endl;
		cout << rb1.size() << endl;
		cout << rb1.IsBanlance() << endl;
	}
}

七、红黑树与 AVL 树的比较

红黑树和 AVL 树都是高效的平衡二叉树 ,增删改查的时间复杂度都是 O(logN),但由于红黑树不追求绝对平衡,只需要保证最长路径不超过最短路径的2倍,所以在某些极端情况下红黑树的查询效率相较于 AVL 树要低一点点;例如当树左子树全部为黑色节点,右子树全部为一黑一红交替时,红黑树的高度差不多是 AVL 树的两倍,但是此时红黑树的查询效率仍然属于 O(logN) 这个量级;

不过也正是由于红黑树不要求左右子树绝对平衡,所以红黑树相较于 AVL 树在插入和删除时的旋转次数要少一些,所以在经常进行增删的结构中红黑树的性能比 AVL 树更优,而且红黑树实现比较简单,所以在实际业务中一般都使用红黑树,而不使用 AVL 树。

相关推荐
Dshuishui1 小时前
我用 Claude Code 做了一个学术论文搜索工具
开发语言·人工智能·python·pip·uv
Resky08181 小时前
ReentrantReadWriteLock 深度解析
java·开发语言·juc
研究点啥好呢1 小时前
Momenta后端开发面试题精选:10道高频考题+答案解析(数据产线方向)
c++·python·面试·求职招聘
Hical612 小时前
C++26 前瞻心得:下一代 C++ 最值得期待的特性
c++
悲伤小伞2 小时前
Linux_传输层协议TCP详解
linux·网络·c++·网络协议·tcp/ip
赏金术士2 小时前
Kotlin 从入门到进阶 之协程 Flow 模块(九)
开发语言·kotlin·php
赵钰老师2 小时前
R语言在生态环境领域中的应用
开发语言·数据分析·r语言
guygg882 小时前
四旋翼无人机串级PID控制MATLAB仿真
开发语言·matlab·无人机
guygg882 小时前
四足液压机器人设计程序MATLAB实现
开发语言·matlab·机器人