【数据结构】红黑树入门指南

阅读说明:全文由浅入深、零基础友好,图文逻辑完整,文末附带代码实战 + 面试速记卡片 + 标准答案题库,可直接用于学习、复盘、面试突击。

一、开场:3张图带你30秒看懂红黑树

1.1 建立直观认知

图1.1 核心对比:BST vs 红黑树

画面:左侧歪扭退化链表(BST插入1,2,3,4,5)、右侧矮胖平衡树形(红黑树同数据)。

核心结论:普通BST会退化,红黑树可以自动维持平衡

图1.2 工业级应用场景

画面:Java、C++、Linux内核三大技术栈标识。

核心结论:红黑树不是学术玩具,是TreeMap、std::map、内核调度、定时器的底层核心数据结构。

图1.3 性能进化路线

BST(会退化O(n))→ 红黑树(自动平衡)→所有操作稳定 O(logn)

1.2 一句话彻底定义红黑树

红黑树 = 带颜色约束的自平衡二叉搜索树,无论如何插入删除,增删查效率永远稳定在 O(logn)。


二、学前热身:为什么一定要用红黑树?

2.1 BST核心规则(基础回顾)

BST唯一核心规则:左小右大

任意节点:左子树所有值 < 当前节点 < 右子树所有值;中序遍历严格升序

图2.1 标准BST结构演示:根10、左5、右15,遍历结果:5 10 15。

2.2 BST的致命缺陷

连续插入 1、2、3、4、5,BST 会变成单向链表,查询效率从 O(logn) 退化到 O(n),数据量越大越卡顿。

2.3 红黑树的解决方案

图2.2 同数据红黑树平衡演示

同样有序数据,红黑树通过红黑颜色约束 + 旋转变色,始终保持矮胖平衡形态,彻底解决链表退化问题。


三、核心原理:5条铁律 + 3个调整工具

3.1 红黑树五条核心规则

核心逻辑 :用颜色限制树形,保证最长路径 ≤ 最短路径 × 2。

【图3.1】基本红黑树样例,后续图片默认叶子节点(NULL)为黑,不额外画

规则对照表

编号 官方规则 通俗人话
节点非红即黑 只有两种颜色
红黑树(RBT)首先得是一棵二叉搜索树(BST) 左 < 根 < 右
根节点、空叶子节点(NIL)都为黑 树顶、没有的节点都算黑色
红节点的孩子都是黑节点 禁止红红相连
所有路径黑色节点数量相同 全局黑高统一

口诀:左根右、根叶黑、不红红、路黑同

工具1:右旋------左儿子上位

图3.2 右旋演示 :将当前节点的左孩子提拉为父节点,原父节点下沉为右孩子。

工具2:左旋------右儿子上位

图3.3 左旋演示 :将当前节点的右孩子提拉为父节点,原父节点下沉为左孩子。

工具3:变色------红黑重分配

图3.4 变色演示叔、父、爷节点变色(红-->黑,黑-->红),将爷节点当作新插入节点重新判断


四、插入操作:看叔叔颜色,3种情况全覆盖

4.1 插入铁律

新节点永远默认红色

原因:插黑色必破坏黑高,一定会修复;插红色仅可能红红冲突,修复概率更低、开销更小

4.2 插入唯一失衡问题

图4.1 红红冲突 :父红 + 子红,违反规则④,修复核心:看叔叔节点颜色

4.3 三种插入失衡场景

情况1:叔叔红色 → 只变色、不旋转

图4.2 叔、父、爷节点变色(红-->黑,黑-->红)冲突上移递归修复

情况2:叔叔黑色 + 节点在外侧(LL/RR)→ 一次旋转+变色

图4.3 直线形态,右旋/左旋爷节点,父爷节点换色,直接终结修复。

情况3:叔叔黑色 + 节点在内侧(LR/RL)→ 两次旋转+变色(以第二次旋转为依据)

图4.4 折线形态,先旋父节点变直线,再旋爷节点,换色修复。

4.4 插入终极口诀

插红节点 → 父黑结束

→ 父红看叔叔 → 叔红,(叔父爷)变色上推

→ 叔黑,分内外,外1旋(父爷)、内2旋(子父爷)


五、删除操作:最难考点!一张流程图吃透4种情况

5.1 删除核心判断

  • 删红色节点:无需修复(不影响黑高)

  • 删黑色节点:必须修复 (路径丢黑,产生双黑缺失

图5.1 删红/删黑对比:删红无事发生,删黑出现黑高缺口,需要向兄弟子树借黑补全。

5.2 修复核心目标

当你删掉一个黑色节点后,这条路径上的黑色节点就少了一个(打破了"所有路径黑节点数相同"的铁律)。为了维持平衡,我们把这缺失的一层"黑"强加在接替它的节点上,这个节点就成了背负两层黑色的**"双黑节点X"。**

消除双黑标记,让整树所有路径黑高重新统一。


5.3 删除决策流程

1. 查找并分类目标节点
  • 首先使用 search 函数寻找待删除节点,如果未找到则无需操作。

  • 若找到该节点,需要根据其子节点的数量将其分类为:零分支(0个孩子)、单分支(1个孩子)或双分支(2个孩子)。

2. 不同分支情况的删除策略

根据目标节点的分支数,红黑树采取不同的处理方案:

  • 双分支节点(2个孩子):

    • 核心思想是**"狸猫换太子"**。

    • 通过寻找替换节点(前驱或后继),将复杂的双分支删除转化为相对简单的"零分支或单分支"删除问题。基础处理逻辑与普通二叉搜索树(BST)相同,仅需追加平衡判断。

  • 单分支节点(1个孩子):

    • 节点特征推导: 在红黑树中,单分支节点本质上就是带有一个孩子的节点。这种节点绝对不可能是红色 。如果是红色会破坏路黑同,如果它是黑色,那么它唯一的孩子必然是红色 ,并且这个红孩子绝对不会再有下一代。因此,单分支节点与其孩子必定是"一黑一红"的组合。

    • 删除方案: 直接让其红色的孩子节点顶替上来,并将其颜色染黑即可。在实际编写代码时,通常的做法是将这个单孩子节点的值拷贝给待删除节点,然后转而去删除那个单独的孩子节点。

  • 零分支节点(0个孩子/叶子节点):

    • 虽然基础摘除动作和 BST 一样,但这里是红黑树删除操作中最复杂、最麻烦的情况,是整个删除算法的"难点大头"。

⭐【图5.2】删除双分支节点示例

狸猫换太子:删除双分支节点转为删除单分支/零分支节点

⭐【图5.2】删除单分支节点示例

⭐【图5.3】删除零分支节点示例

  • 待删除节点有黑兄弟且黑兄弟有红孩子节点
  • 待删除节点有红兄弟

⭐【示例一】

⭐【示例二】


**⭐**5.4 删除决策流程图


六、完整可运行C源码(可直接复制测试)

6.1 节点定义

cpp 复制代码
typedef int ElemType;

typedef enum ColorType {
	RED,
	BLACK
}ColorType;

//红黑树有效节点
typedef struct RBNode {
	ElemType data;				//数据域
	struct RBNode* leftchild;	//左孩子指针域
	struct RBNode* rightchild;	//右孩子指针域
	struct RBNode* parent;		//双亲指针域
	ColorType color;			//节点的颜色(红/黑)
}RBNode;

//辅助节点
typedef struct RBTree {
	struct RBNode* root;		//根节点指针域
};

6.2 基础旋转工具

cpp 复制代码
//2.左旋
RBNode* LeftRotate(RBNode* node)
{
	RBNode* child = node->rightchild;
	RBNode* grandchild = node->rightchild->leftchild;

	node->rightchild = grandchild;
	if (grandchild != NULL)
		grandchild->parent = node;

	child->leftchild = node;
	node->parent = child;

	return child;
}

//3.右旋
RBNode* RightRotate(RBNode* node)
{
	RBNode* child = node->leftchild;
	RBNode* grandchild = node->leftchild->rightchild;

	node->leftchild = grandchild;
	if (grandchild != NULL)
		grandchild->parent = node;

	child->rightchild = node;
	node->parent = child;

	return child;
}

6.3 插入 + 平衡修复

cpp 复制代码
//4.插入
bool Insert_RB(RBTree* pTree, ElemType val)
{
	//先把节点按照BST规则落位

	//1.先申请两个指针p和pp,分别指向根节点和root
	RBNode* p = pTree->root;
	RBNode* pp = NULL;

	//1.5 专门用来处理原本可能是一颗空树
	if (pTree->root == NULL)
	{
		pTree->root = BuyNode(val);
		pTree->root->color = BLACK;
		return true;
	}

	//2.进入while循环,如果p指向的当前节点存在,但是值不等于val
	while (p != NULL && p->data != val)
	{
		pp = p;
		if (val < p->data)
			p = p->leftchild;
		else
			p = p->rightchild;
	}

	//3.当while循环,代表着要么找到,要么没找到
	//要是找到 => 不用插入了
	if (p != NULL && val == p->data)
		return true;

	//4.此时代码指向到这一行,表示p走到了NULL,也说明val值节点里面不存在 => 往里面插入
	RBNode* pnewnode = BuyNode(val);

	if (pnewnode->data < pp->data)
		pp->leftchild = pnewnode;
	else
		pp->rightchild = pnewnode;
	pnewnode->parent = pp;


	//插入成功了
	//5.此时pnewnode节点的父节点一定存在,但是颜色有可能是红,有可能是黑
	if (pp->color == RED)
	{
		Insert_Adjust(pTree, pnewnode);
	}

	return true;
}

//5.插入的平衡调整函数
void Insert_Adjust(RBTree* pTree, RBNode* node)
{
	//1.如果node节点的父节点不存在(是根节点),则直接颜色变黑结束
	if (node->parent == NULL)
	{
		node->color = BLACK;
		return;
	}

	//如果代码执行到了这里,说明node节点的父节点存在,则进一步去看其父节点颜色
	RBNode* father = node->parent;//100%存在

	//2.如果该节点的父节点存在,且是黑色 => 无需调整
	if (father->color == BLACK)
	{
		return;
	}

	//3.如果该节点的父节点存在,且是红色 => 违反不红红,进一步先观察其叔叔的情况
	RBNode* grandfather = father->parent;//100%
	RBNode* uncle = father == grandfather->leftchild ? grandfather->rightchild : grandfather->leftchild;//50%

	//3.1 如果该节点的叔叔节点存在且是红色 => 叔父爷变色,然后将其爷爷节点进行相同的逻辑处理
	if (uncle != NULL && uncle->color == RED)
	{
		uncle->color = BLACK;
		father->color = BLACK;
		grandfather->color = RED;

		Insert_Adjust(pTree, grandfather);
		return;
	}

	//3.2 如果该节点的叔叔节点不存在(不存在就按照空节点处理,而空节点默认黑色) 或者 该节点的叔叔节点存在且颜色是黑色
	//判断LL LR RR RL   
	if (grandfather->leftchild == father)//L
	{
		if (father->leftchild == node)//LL
		{
			//单右旋

			//太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = RightRotate(grandfather);


			Rotate_ReturnNode(pTree, great_grandfather, tmp);

			//变色
			father->color = BLACK;
			grandfather->color = RED;
		}
		else//LR
		{
			//先左旋
			grandfather->leftchild = LeftRotate(father);
			//再右旋
		   //太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = RightRotate(grandfather);

			Rotate_ReturnNode(pTree, great_grandfather, tmp);

			//变色
			grandfather->color = RED;
			node->color = BLACK;
		}
	}

	if (grandfather->rightchild == father)//R
	{
		if (father->rightchild == node)//RR
		{
			//单左旋

			//太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = LeftRotate(grandfather);


			Rotate_ReturnNode(pTree, great_grandfather, tmp);

			//变色
			father->color = BLACK;
			grandfather->color = RED;

		}
		else//RL
		{
			//先右旋
			grandfather->rightchild = RightRotate(father);
			//再左旋
		   //太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = LeftRotate(grandfather);


			Rotate_ReturnNode(pTree, great_grandfather, tmp);


			//变色
			grandfather->color = RED;
			node->color = BLACK;
		}
	}
	return;
}


//8.旋转操作的返回节点接收处理
void Rotate_ReturnNode(RBTree* pTree, RBNode* greatgrandfather, RBNode* tmp)
{
	if (greatgrandfather == NULL)
	{
		pTree->root = tmp;
	}
	else
	{
		if (tmp->data < greatgrandfather->data)
			greatgrandfather->leftchild = tmp;
		else
			greatgrandfather->rightchild = tmp;
	}
	tmp->parent = greatgrandfather;
}

6.4 删除 + 双黑修复

cpp 复制代码
//6.删除
bool Delete_RB(RBTree* pTree, ElemType val)
{
	//1.先找到待删除节点
	RBNode* p = Search_RB(pTree->root, val);
	if (p == NULL)
		return true;

	//2.如果该待删除节点存在,则进一步区分其是0/1/2
	//2.1 如果是2分支 => 狸猫换太子
	if (p->leftchild != NULL && p->rightchild != NULL)
	{
		//直接后继节点来 当做 狸猫
		RBNode* cat = p->rightchild;
		while (cat->leftchild != NULL)
			cat = cat->leftchild;

		p->data = cat->data;
		p = cat;
	}

	//2.2 如果是1分支 => 让其红色孩子顶上来,然后变黑即可
	if (p->leftchild != NULL || p->rightchild != NULL)
	{
		RBNode* child = p->leftchild != NULL ? p->leftchild : p->rightchild;
		p->data = child->data;

		p->leftchild = p->rightchild = NULL;
		free(child);
		child = NULL;

		return true;
	}

	//2.3 0分支删除
	//1.防止该0分支节点,是根节点(如果该节点即是0分支,又是根节点,说明
	// 整体树只有这一个节点) => 直接释放,然后辅助节点的root
	if (p->parent == NULL)
	{
		free(p);
		p = NULL;
		pTree->root = NULL;
		return true;
	}


	//2.如果是红色 => 直接释放,无需调整
	if (p->color == RED)
	{
		RBNode* father = p->parent;
		if (p->data < father->data)
			father->leftchild = NULL;
		else
			father->rightchild = NULL;
		free(p);
		p = NULL;
		return true;
	}

	//3.如果是黑色 => 很麻烦,不急
	Delete_Adjust2(pTree, p, true);
	return true;
}

//7.删除的平衡调整函数
void Delete_Adjust(RBTree* pTree, RBNode* node)
{
	//1.申请两个指针 father,sibling 指向其父节点和其兄弟节点
	RBNode* father = node->parent;
	RBNode* sibling = node == father->leftchild ? father->rightchild : father->leftchild;

	//2.兄弟节点如果是红色 => 父兄变色,然后父节点朝着待删除节点一侧进行单旋,此时待删除节点就会出现一个新的黑兄弟节点
	if (sibling->color == RED)
	{
		father->color = RED;
		sibling->color = BLACK;

		RBNode* grandfather = father->parent;
		RBNode* tmp = NULL;
		if (node == father->leftchild)
			tmp = LeftRotate(father);
		else
			tmp = RightRotate(father);
		Rotate_ReturnNode(pTree, grandfather, tmp);

		Delete_Adjust(pTree, node);
		return;
	}
	else//兄弟节点如果是黑色
	{
		//进一步 判断其兄弟节点是否有无红孩
		RBNode* redchild = NULL;
		if (sibling->leftchild != NULL && sibling->leftchild->color == RED)
			redchild = sibling->leftchild;
		else if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
			redchild = sibling->rightchild;
		else
			redchild = NULL;

		//一旦确定待删除节点是无孩黑色节点,它的兄弟也是黑色,则后续用不到待删除node节点,可以先提前释放掉
		free(node);
		if (father->leftchild == node)
			father->leftchild = NULL;
		else
			father->rightchild = NULL;

		if (redchild == NULL) //没有红色孩子 => 兄弟变红,然后对其父节点情况接着判断
		{
			sibling->color = RED;
			if (father->parent = NULL)
			{
				return;
			}
			else if (father->color == RED)
			{
				father->color = BLACK;
				return;
			}
			else
			{
				Delete_Adjust(pTree, father); //这里的father不应该被删除,只是对其兄弟做判断,然后调整
				return;
			}

		}
		else//有红色孩子 (注意:如果有两个红色孩子, 型号判定的时候 能判LL/RR就不判LR/RL)
		{
			//LL RR LR RL
			if (father->leftchild == sibling)//L
			{
				if (sibling->leftchild == redchild)//LL
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//LR
				{
					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先左旋
					father->leftchild = LeftRotate(sibling);

					//再右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}


			if (father->rightchild == sibling)//R
			{
				//第一个字母判断是R,则进一步要修正一下redchild的指向(有可能有两个红孩,但是redchidl默认指向左侧,也及时先判的是RL)
				if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
				{
					redchild = sibling->rightchild;
				}


				if (sibling->rightchild == redchild)//RR
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//RL
				{


					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先右旋
					father->leftchild = RightRotate(sibling);

					//再左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}
		}
	}
}

void Delete_Adjust2(RBTree* pTree, RBNode* node, bool tag)//当tag为真,node应该被删
{
	//1.申请两个指针 father,sibling 指向其父节点和其兄弟节点
	RBNode* father = node->parent;
	RBNode* sibling = node == father->leftchild ? father->rightchild : father->leftchild;

	//2.兄弟节点如果是红色 => 父兄变色,然后父节点朝着待删除节点一侧进行单旋,此时待删除节点就会出现一个新的黑兄弟节点
	if (sibling->color == RED)
	{
		father->color = RED;
		sibling->color = BLACK;

		RBNode* grandfather = father->parent;
		RBNode* tmp = NULL;
		if (node == father->leftchild)
			tmp = LeftRotate(father);
		else
			tmp = RightRotate(father);
		Rotate_ReturnNode(pTree, grandfather, tmp);

		Delete_Adjust2(pTree, node, true);
		return;
	}
	else//兄弟节点如果是黑色
	{
		//进一步 判断其兄弟节点是否有无红孩

		RBNode* redchild = NULL;
		if (sibling->leftchild != NULL && sibling->leftchild->color == RED)
			redchild = sibling->leftchild;
		else if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
			redchild = sibling->rightchild;
		else
			redchild = NULL;

		//一旦确定待删除节点是无孩黑色节点,它的兄弟也是黑色,则后续用不到待删除node节点,可以先提前释放掉
		if (tag)
		{
			free(node);
			if (father->leftchild == node)
				father->leftchild = NULL;
			else
				father->rightchild = NULL;
		}


		if (redchild == NULL) //没有红色孩子 => 兄弟变红,然后对其父节点情况接着判断
		{
			sibling->color = RED;
			if (father->parent == NULL) //情况一:father是根
			{
				return;
			}
			else if (father->color == RED)//情况二:father不是根,但是是红色
			{
				father->color = BLACK;
				return;
			}
			else//情况三:father不是根,但是是黑色
			{
				Delete_Adjust2(pTree, father, false); //这里的father不应该被删除,只是对其兄弟做判断,然后调整
				return;
			}

		}
		else//有红色孩子 (注意:如果有两个红色孩子, 型号判定的时候 能判LL/RR就不判LR/RL)
		{
			//LL RR LR RL
			if (father->leftchild == sibling)//L
			{
				if (sibling->leftchild == redchild)//LL
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//LR
				{
					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先左旋
					father->leftchild = LeftRotate(sibling);

					//再右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}


			if (father->rightchild == sibling)//R
			{
				//第一个字母判断是R,则进一步要修正一下redchild的指向(有可能有两个红孩,但是redchidl默认指向左侧,也及时先判的是RL)
				if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
				{
					redchild = sibling->rightchild;
				}


				if (sibling->rightchild == redchild)//RR
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//RL
				{


					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先右旋
					father->leftchild = RightRotate(sibling);

					//再左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}
		}
	}
}

6.5 遍历验证 (中序)

cpp 复制代码
//3.打印(中序非递归)
#include <stack>
void Show_InOrderRB(RBNode* root)
{
	if (root == NULL)
		return;
	std::stack<RBNode* >st;
	st.push(root);
	bool tag = true;

	while (!st.empty())
	{
		while (tag && st.top()->leftchild != NULL)
			st.push(st.top()->leftchild);

		RBNode* tmp = st.top();
		st.pop();
		printf("%d ", tmp->data);

		if (tmp->rightchild != NULL)
		{
			st.push(tmp->rightchild);
			tag = true;
		}
		else
		{
			tag = false;
		}
	}
}

附录A:高频面试题 + 标准答案

一、基础概念题

Q1:红黑树五大性质?

答:①节点非红即黑;②左<根<右;③根、空叶子节点为黑色;④红节点孩子必黑;⑤所有路径黑高数量一致。

Q2:新节点为什么默认红色?

答:黑色节点会破坏全局黑高,必定触发修复;红色节点仅可能红红冲突,修复开销最小

Q3:根节点为什么必须黑色?

答:统一全局黑高基准,避免递归上浮后根节点变红,保证整树平衡规则生效。

二、原理深度题

Q4:为什么最长路径不超过最短路径2倍?

答:无连续红节点,最短路径全黑、最长路径红黑交替,同等黑高下,长度最大比例为 1:2。

Q5:插入和删除失衡的区别?

答:插入只破坏红红规则;删除破坏黑高平衡,是红黑树最复杂场景。

三、对比选型题

Q6:红黑树和AVL树区别与选型?

答:AVL严格平衡、查询快、增删开销大;红黑树弱平衡、旋转极少、读写均衡。频繁增删选红黑树,静态查询选AVL树

Q7:红黑树和哈希表对比?

答:哈希表O(1)无序;红黑树O(logn)有序、支持区间遍历。要速度选哈希表,要有序稳定选红黑树

四、工程应用题

Q8:TreeMap为什么用红黑树不用AVL?

答:集合类频繁增删,红黑树旋转次数更少、写性能更强,综合性能更优。

Q9:Linux内核为什么用红黑树?

答:内核资源动态增减频繁,需要稳定O(logn)增删查,红黑树开销低、稳定性高。


附录B:完整可测试代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <memory.h>

#include "RBTree.h"

//工具函数
//1.购买新节点
RBNode* BuyNode(ElemType val)
{
	RBNode* pnewnode = (RBNode*)malloc(sizeof(RBNode));
	if (NULL == pnewnode)
		exit(EXIT_FAILURE);

	memset(pnewnode, 0, sizeof(RBNode));
	pnewnode->data = val;
	return pnewnode;
}

//2.左旋
RBNode* LeftRotate(RBNode* node)
{
	RBNode* child = node->rightchild;
	RBNode* grandchild = node->rightchild->leftchild;

	node->rightchild = grandchild;
	if (grandchild != NULL)
		grandchild->parent = node;

	child->leftchild = node;
	node->parent = child;

	return child;
}

//3.右旋
RBNode* RightRotate(RBNode* node)
{
	RBNode* child = node->leftchild;
	RBNode* grandchild = node->leftchild->rightchild;

	node->leftchild = grandchild;
	if (grandchild != NULL)
		grandchild->parent = node;

	child->rightchild = node;
	node->parent = child;

	return child;
}

//普通函数
//1.初始化
void Init_RB(RBTree* pTree)
{
	assert(pTree != NULL);
	pTree->root = NULL;
}

//2.查找
RBNode* Search_RB(RBNode* root, ElemType val)
{
	RBNode* p = root;

	while (p != NULL && p->data != val)
	{
		if (val < p->data)
			p = p->leftchild;
		else
			p = p->rightchild;
	}
	return p;
}

//3.打印(中序非递归)
#include <stack>
void Show_InOrderRB(RBNode* root)
{
	if (root == NULL)
		return;
	std::stack<RBNode* >st;
	st.push(root);
	bool tag = true;

	while (!st.empty())
	{
		while (tag && st.top()->leftchild != NULL)
			st.push(st.top()->leftchild);

		RBNode* tmp = st.top();
		st.pop();
		printf("%d ", tmp->data);

		if (tmp->rightchild != NULL)
		{
			st.push(tmp->rightchild);
			tag = true;
		}
		else
		{
			tag = false;
		}
	}
}

//4.插入
bool Insert_RB(RBTree* pTree, ElemType val)
{
	//先把节点按照BST规则落位

	//1.先申请两个指针p和pp,分别指向根节点和root
	RBNode* p = pTree->root;
	RBNode* pp = NULL;

	//1.5 专门用来处理原本可能是一颗空树
	if (pTree->root == NULL)
	{
		pTree->root = BuyNode(val);
		pTree->root->color = BLACK;
		return true;
	}

	//2.进入while循环,如果p指向的当前节点存在,但是值不等于val
	while (p != NULL && p->data != val)
	{
		pp = p;
		if (val < p->data)
			p = p->leftchild;
		else
			p = p->rightchild;
	}

	//3.当while循环,代表着要么找到,要么没找到
	//要是找到 => 不用插入了
	if (p != NULL && val == p->data)
		return true;

	//4.此时代码指向到这一行,表示p走到了NULL,也说明val值节点里面不存在 => 往里面插入
	RBNode* pnewnode = BuyNode(val);

	if (pnewnode->data < pp->data)
		pp->leftchild = pnewnode;
	else
		pp->rightchild = pnewnode;
	pnewnode->parent = pp;


	//插入成功了
	//5.此时pnewnode节点的父节点一定存在,但是颜色有可能是红,有可能是黑
	if (pp->color == RED)
	{
		Insert_Adjust(pTree, pnewnode);
	}

	return true;
}

#if 0
////5.插入的平衡调整函数
//void Insert_Adjust(RBTree* pTree, RBNode* node)
//{
//    //1.如果node节点的父节点不存在(是根节点),则直接颜色变黑结束
//    if (node->parent == NULL)
//    {
//        node->color = BLACK;
//        return;
//    }
//        
//    //如果代码执行到了这里,说明node节点的父节点存在,则进一步去看其父节点颜色
//    RBNode* father = node->parent;//100%存在
//   
//    //2.如果该节点的父节点存在,且是黑色 => 无需调整
//    if (father->color == BLACK)
//    {
//        return;
//    }
//
//    //3.如果该节点的父节点存在,且是红色 => 违反不红红,进一步先观察其叔叔的情况
//    RBNode* grandfather = father->parent;//100%
//    RBNode* uncle = father == grandfather->leftchild ? grandfather->rightchild : grandfather->leftchild;//50%
//    
//    //3.1 如果该节点的叔叔节点存在且是红色 => 叔父爷变色,然后将其爷爷节点进行相同的逻辑处理
//    if (uncle != NULL && uncle->color == RED)
//    {
//        uncle->color = BLACK;
//        father->color = BLACK;
//        grandfather->color = RED;
//
//        Insert_Adjust(pTree, grandfather);
//        return;
//    }
//
//    //3.2 如果该节点的叔叔节点不存在(不存在就按照空节点处理,而空节点默认黑色) 或者 该节点的叔叔节点存在且颜色是黑色
//    //判断LL LR RR RL   
//    if (grandfather->leftchild == father)//L
//    {
//        if (father->leftchild == node)//LL
//        {
//            //单右旋
//
//            //太爷爷的赋值 一定要在 旋转函数的上面执行
//            RBNode* great_grandfather = grandfather->parent;//50% //
//            RBNode*tmp = RightRotate(grandfather);
//            
//            
//            //太爷如果不在 辅助节点接收
//            if (great_grandfather == NULL)
//            {
//                pTree->root = tmp;
//            }
//            //太爷如果在 太爷节点   
//            else
//            {
//                if (tmp->data < great_grandfather->data)
//                    great_grandfather->leftchild = tmp;
//                else
//                    great_grandfather->rightchild = tmp;
//            }
//
//            tmp->parent = great_grandfather;
//
//            //变色
//            father->color = BLACK;
//            grandfather->color = RED;
//        }
//        else//LR
//        {
//            //先左旋
//           grandfather->leftchild = LeftRotate(father);
//            //再右旋
//           //太爷爷的赋值 一定要在 旋转函数的上面执行
//           RBNode* great_grandfather = grandfather->parent;//50% //
//           RBNode*tmp =  RightRotate(grandfather);
//           //太爷如果不在 辅助节点接收
//           if (great_grandfather == NULL)
//           {
//               pTree->root = tmp;
//           }
//           //太爷如果在 太爷节点   
//           else
//           {
//               if (tmp->data < great_grandfather->data)
//                   great_grandfather->leftchild = tmp;
//               else
//                   great_grandfather->rightchild = tmp;
//           }
//
//           tmp->parent = great_grandfather;
//
//           //变色
//           grandfather->color = RED;
//           node->color = BLACK;
//        }
//    }
//
//    if (grandfather->rightchild == father)//R
//    {
//        if (father->rightchild == node)//RR
//        {
//            //单左旋
//
//            //太爷爷的赋值 一定要在 旋转函数的上面执行
//            RBNode* great_grandfather = grandfather->parent;//50% //
//            RBNode* tmp = LeftRotate(grandfather);
//
//
//            //太爷如果不在 辅助节点接收
//            if (great_grandfather == NULL)
//            {
//                pTree->root = tmp;
//            }
//            //太爷如果在 太爷节点   
//            else
//            {
//                if (tmp->data < great_grandfather->data)
//                    great_grandfather->leftchild = tmp;
//                else
//                    great_grandfather->rightchild = tmp;
//            }
//
//            tmp->parent = great_grandfather;
//
//            //变色
//            father->color = BLACK;
//            grandfather->color = RED;
//
//        }
//        else//RL
//        {
//            //先右旋
//            grandfather->rightchild = RightRotate(father);
//            //再左旋
//           //太爷爷的赋值 一定要在 旋转函数的上面执行
//            RBNode* great_grandfather = grandfather->parent;//50% //
//            RBNode* tmp = LeftRotate(grandfather);
//            //太爷如果不在 辅助节点接收
//            if (great_grandfather == NULL)
//            {
//                pTree->root = tmp;
//            }
//            //太爷如果在 太爷节点   
//            else
//            {
//                if (tmp->data < great_grandfather->data)
//                    great_grandfather->leftchild = tmp;
//                else
//                    great_grandfather->rightchild = tmp;
//            }
//
//            tmp->parent = great_grandfather;
//
//            //变色
//            grandfather->color = RED;
//            node->color = BLACK;
//        }
//       
//    }
//
//    return;
//}
#endif


//5.插入的平衡调整函数
void Insert_Adjust(RBTree* pTree, RBNode* node)
{
	//1.如果node节点的父节点不存在(是根节点),则直接颜色变黑结束
	if (node->parent == NULL)
	{
		node->color = BLACK;
		return;
	}

	//如果代码执行到了这里,说明node节点的父节点存在,则进一步去看其父节点颜色
	RBNode* father = node->parent;//100%存在

	//2.如果该节点的父节点存在,且是黑色 => 无需调整
	if (father->color == BLACK)
	{
		return;
	}

	//3.如果该节点的父节点存在,且是红色 => 违反不红红,进一步先观察其叔叔的情况
	RBNode* grandfather = father->parent;//100%
	RBNode* uncle = father == grandfather->leftchild ? grandfather->rightchild : grandfather->leftchild;//50%

	//3.1 如果该节点的叔叔节点存在且是红色 => 叔父爷变色,然后将其爷爷节点进行相同的逻辑处理
	if (uncle != NULL && uncle->color == RED)
	{
		uncle->color = BLACK;
		father->color = BLACK;
		grandfather->color = RED;

		Insert_Adjust(pTree, grandfather);
		return;
	}

	//3.2 如果该节点的叔叔节点不存在(不存在就按照空节点处理,而空节点默认黑色) 或者 该节点的叔叔节点存在且颜色是黑色
	//判断LL LR RR RL   
	if (grandfather->leftchild == father)//L
	{
		if (father->leftchild == node)//LL
		{
			//单右旋

			//太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = RightRotate(grandfather);


			Rotate_ReturnNode(pTree, great_grandfather, tmp);

			//变色
			father->color = BLACK;
			grandfather->color = RED;
		}
		else//LR
		{
			//先左旋
			grandfather->leftchild = LeftRotate(father);
			//再右旋
		   //太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = RightRotate(grandfather);

			Rotate_ReturnNode(pTree, great_grandfather, tmp);

			//变色
			grandfather->color = RED;
			node->color = BLACK;
		}
	}

	if (grandfather->rightchild == father)//R
	{
		if (father->rightchild == node)//RR
		{
			//单左旋

			//太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = LeftRotate(grandfather);


			Rotate_ReturnNode(pTree, great_grandfather, tmp);

			//变色
			father->color = BLACK;
			grandfather->color = RED;

		}
		else//RL
		{
			//先右旋
			grandfather->rightchild = RightRotate(father);
			//再左旋
		   //太爷爷的赋值 一定要在 旋转函数的上面执行
			RBNode* great_grandfather = grandfather->parent;//50% //
			RBNode* tmp = LeftRotate(grandfather);


			Rotate_ReturnNode(pTree, great_grandfather, tmp);


			//变色
			grandfather->color = RED;
			node->color = BLACK;
		}
	}
	return;
}


//8.旋转操作的返回节点接收处理
void Rotate_ReturnNode(RBTree* pTree, RBNode* greatgrandfather, RBNode* tmp)
{
	if (greatgrandfather == NULL)
	{
		pTree->root = tmp;
	}
	else
	{
		if (tmp->data < greatgrandfather->data)
			greatgrandfather->leftchild = tmp;
		else
			greatgrandfather->rightchild = tmp;
	}
	tmp->parent = greatgrandfather;
}

//6.删除
bool Delete_RB(RBTree* pTree, ElemType val)
{
	//1.先找到待删除节点
	RBNode* p = Search_RB(pTree->root, val);
	if (p == NULL)
		return true;

	//2.如果该待删除节点存在,则进一步区分其是0/1/2
	//2.1 如果是2分支 => 狸猫换太子
	if (p->leftchild != NULL && p->rightchild != NULL)
	{
		//直接后继节点来 当做 狸猫
		RBNode* cat = p->rightchild;
		while (cat->leftchild != NULL)
			cat = cat->leftchild;

		p->data = cat->data;
		p = cat;
	}

	//2.2 如果是1分支 => 让其红色孩子顶上来,然后变黑即可
	if (p->leftchild != NULL || p->rightchild != NULL)
	{
		RBNode* child = p->leftchild != NULL ? p->leftchild : p->rightchild;
		p->data = child->data;

		p->leftchild = p->rightchild = NULL;
		free(child);
		child = NULL;

		return true;
	}

	//2.3 0分支删除
	//1.防止该0分支节点,是根节点(如果该节点即是0分支,又是根节点,说明
	// 整体树只有这一个节点) => 直接释放,然后辅助节点的root
	if (p->parent == NULL)
	{
		free(p);
		p = NULL;
		pTree->root = NULL;
		return true;
	}


	//2.如果是红色 => 直接释放,无需调整
	if (p->color == RED)
	{
		RBNode* father = p->parent;
		if (p->data < father->data)
			father->leftchild = NULL;
		else
			father->rightchild = NULL;
		free(p);
		p = NULL;
		return true;
	}

	//3.如果是黑色 => 很麻烦,不急
	Delete_Adjust2(pTree, p, true);
	return true;
}

//7.删除的平衡调整函数
void Delete_Adjust(RBTree* pTree, RBNode* node)
{
	//1.申请两个指针 father,sibling 指向其父节点和其兄弟节点
	RBNode* father = node->parent;
	RBNode* sibling = node == father->leftchild ? father->rightchild : father->leftchild;

	//2.兄弟节点如果是红色 => 父兄变色,然后父节点朝着待删除节点一侧进行单旋,此时待删除节点就会出现一个新的黑兄弟节点
	if (sibling->color == RED)
	{
		father->color = RED;
		sibling->color = BLACK;

		RBNode* grandfather = father->parent;
		RBNode* tmp = NULL;
		if (node == father->leftchild)
			tmp = LeftRotate(father);
		else
			tmp = RightRotate(father);
		Rotate_ReturnNode(pTree, grandfather, tmp);

		Delete_Adjust(pTree, node);
		return;
	}
	else//兄弟节点如果是黑色
	{
		//进一步 判断其兄弟节点是否有无红孩
		RBNode* redchild = NULL;
		if (sibling->leftchild != NULL && sibling->leftchild->color == RED)
			redchild = sibling->leftchild;
		else if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
			redchild = sibling->rightchild;
		else
			redchild = NULL;

		//一旦确定待删除节点是无孩黑色节点,它的兄弟也是黑色,则后续用不到待删除node节点,可以先提前释放掉
		free(node);
		if (father->leftchild == node)
			father->leftchild = NULL;
		else
			father->rightchild = NULL;

		if (redchild == NULL) //没有红色孩子 => 兄弟变红,然后对其父节点情况接着判断
		{
			sibling->color = RED;
			if (father->parent = NULL)
			{
				return;
			}
			else if (father->color == RED)
			{
				father->color = BLACK;
				return;
			}
			else
			{
				Delete_Adjust(pTree, father); //这里的father不应该被删除,只是对其兄弟做判断,然后调整
				return;
			}

		}
		else//有红色孩子 (注意:如果有两个红色孩子, 型号判定的时候 能判LL/RR就不判LR/RL)
		{
			//LL RR LR RL
			if (father->leftchild == sibling)//L
			{
				if (sibling->leftchild == redchild)//LL
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//LR
				{
					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先左旋
					father->leftchild = LeftRotate(sibling);

					//再右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}


			if (father->rightchild == sibling)//R
			{
				//第一个字母判断是R,则进一步要修正一下redchild的指向(有可能有两个红孩,但是redchidl默认指向左侧,也及时先判的是RL)
				if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
				{
					redchild = sibling->rightchild;
				}


				if (sibling->rightchild == redchild)//RR
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//RL
				{


					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先右旋
					father->leftchild = RightRotate(sibling);

					//再左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}
		}
	}
}

void Delete_Adjust2(RBTree* pTree, RBNode* node, bool tag)//当tag为真,node应该被删
{
	//1.申请两个指针 father,sibling 指向其父节点和其兄弟节点
	RBNode* father = node->parent;
	RBNode* sibling = node == father->leftchild ? father->rightchild : father->leftchild;

	//2.兄弟节点如果是红色 => 父兄变色,然后父节点朝着待删除节点一侧进行单旋,此时待删除节点就会出现一个新的黑兄弟节点
	if (sibling->color == RED)
	{
		father->color = RED;
		sibling->color = BLACK;

		RBNode* grandfather = father->parent;
		RBNode* tmp = NULL;
		if (node == father->leftchild)
			tmp = LeftRotate(father);
		else
			tmp = RightRotate(father);
		Rotate_ReturnNode(pTree, grandfather, tmp);

		Delete_Adjust2(pTree, node, true);
		return;
	}
	else//兄弟节点如果是黑色
	{
		//进一步 判断其兄弟节点是否有无红孩

		RBNode* redchild = NULL;
		if (sibling->leftchild != NULL && sibling->leftchild->color == RED)
			redchild = sibling->leftchild;
		else if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
			redchild = sibling->rightchild;
		else
			redchild = NULL;

		//一旦确定待删除节点是无孩黑色节点,它的兄弟也是黑色,则后续用不到待删除node节点,可以先提前释放掉
		if (tag)
		{
			free(node);
			if (father->leftchild == node)
				father->leftchild = NULL;
			else
				father->rightchild = NULL;
		}


		if (redchild == NULL) //没有红色孩子 => 兄弟变红,然后对其父节点情况接着判断
		{
			sibling->color = RED;
			if (father->parent == NULL) //情况一:father是根
			{
				return;
			}
			else if (father->color == RED)//情况二:father不是根,但是是红色
			{
				father->color = BLACK;
				return;
			}
			else//情况三:father不是根,但是是黑色
			{
				Delete_Adjust2(pTree, father, false); //这里的father不应该被删除,只是对其兄弟做判断,然后调整
				return;
			}

		}
		else//有红色孩子 (注意:如果有两个红色孩子, 型号判定的时候 能判LL/RR就不判LR/RL)
		{
			//LL RR LR RL
			if (father->leftchild == sibling)//L
			{
				if (sibling->leftchild == redchild)//LL
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//LR
				{
					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先左旋
					father->leftchild = LeftRotate(sibling);

					//再右旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = RightRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}


			if (father->rightchild == sibling)//R
			{
				//第一个字母判断是R,则进一步要修正一下redchild的指向(有可能有两个红孩,但是redchidl默认指向左侧,也及时先判的是RL)
				if (sibling->rightchild != NULL && sibling->rightchild->color == RED)
				{
					redchild = sibling->rightchild;
				}


				if (sibling->rightchild == redchild)//RR
				{
					//变色: r变s s变p p变黑
					redchild->color = sibling->color;
					sibling->color = father->color;
					father->color = BLACK;

					//单左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
				else//RL
				{


					//变色: r变p p变黑
					redchild->color = father->color;
					father->color = BLACK;

					//先右旋
					father->leftchild = RightRotate(sibling);

					//再左旋
					RBNode* grandfather = father->parent;
					RBNode* tmp = LeftRotate(father);
					Rotate_ReturnNode(pTree, grandfather, tmp);
					return;
				}
			}
		}
	}
}

#if 0
int main()
{
	RBTree head;
	Init_RB(&head);

	Insert_RB(&head, 17);
	Insert_RB(&head, 18);
	Insert_RB(&head, 23);
	Insert_RB(&head, 34);
	Insert_RB(&head, 27);
	Insert_RB(&head, 15);
	Insert_RB(&head, 9);
	Insert_RB(&head, 6);
	Insert_RB(&head, 8);
	Insert_RB(&head, 5);
	Insert_RB(&head, 25);

	Show_InOrderRB(head.root);
	printf("\n");

	//Delete_RB(&head, 25);//无孩 红色节点
	//Delete_RB(&head, 6);//单分支
	//Delete_RB(&head, 18);//双分支 -> 转化为单分支删除
	//Delete_RB(&head, 15);//双分支 -> 转化为0分支黑色节点,且兄弟是红色的删除
	//Delete_RB(&head, 9);//0分支黑色节点,且兄弟是黑色且有红孩的删除 (LL型)
	//Delete_RB(&head, 34);//0分支黑色节点,且兄弟是黑色且有红孩的删除 (LR型)

	Delete_RB(&head, 5);
	Delete_RB(&head, 6);//0分支黑色节点,且兄弟是黑色且有没有一个红孩的删除  
	Show_InOrderRB(head.root);
	printf("\n");


	return 0;
}
#endif

全文总结

  1. 本质:红黑树通过颜色约束实现弱平衡,用极小查询损耗换取极低的修改开销。

  2. 学习核心:插入修红红冲突,删除修黑高缺失,所有场景均可推导,无需死记。

  3. 工程价值:内存级有序数据首选结构,是Java集合、C++STL、操作系统内核的底层基石。

相关推荐
神奇小汤圆1 小时前
JDK 8 → 17 → 21 → 25:一次性讲清四代版本的关键跃迁
后端
2601_949817721 小时前
C++指针与引用深度精讲:底层原理、差异对比与高阶实战陷阱
java·jvm·c++
要阿尔卑斯吗1 小时前
无限深度章节树如何一遍建成?路径编码+单调栈
后端
大东_tom1 小时前
Python 与 PyTorch 工程实践——从脚本到训练框架
后端
Joose都有人用1 小时前
我把一个「九成正常、一成作恶」的风控难题做稳了,聊聊踩过的坑
后端
烟锁池塘柳03 小时前
【C/C++】解决C++控制台输出中文乱码问题
c语言·开发语言·c++
:-)3 小时前
基础算法-选择排序
数据结构·算法·排序算法
zh路西法3 小时前
【10天速通Navigation2】(九):LQR最优控制器的原理推导与Nav2插件实现
c++·ros2·最优控制·lqr·navigation2
她说彩礼65万3 小时前
Asp.net core返回类型总结
后端·asp.net