红黑树原理(增加节点、删除节点、修改节点、查询节点)

本文详细介绍了红黑树的插入、删除和查找操作。在插入过程中,根据红黑树性质调整节点颜色和旋转;删除时,根据节点颜色和子节点情况处理,保持红黑树性质;查找遵循二分查找原则。示例展示了具体操作步骤和树结构变化。

红黑树定义

 1. 节点颜色非红即黑
 2. 根节点颜色是黑色
 3. 叶子节点(空节点)颜色为黑色
 4. 红色节点的子节点(包括空节点)为黑色
 5. 某个节点下的所有路径,有相同的黑色节点(包括空节点(黑色))

在这里插入图片描述

红黑树增删改查演示地址https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
示例数据:12,1,9,2,3,0,11,7,19,4,15,18,5,14,13,10,16,6,8,17

1. 红黑树增加节点过程

 增加一个节点,节点颜色默认为红色,对红黑树进行二分查找,对比,
 直至整个红黑树的叶子节点,确定当前节点插入红黑树的初始位置(插入节点
 初始位置为整个红黑树的叶子节点)

	1、判断插入节点是否为根节点,如果是根节点
		把当前节点置为根节点,并把颜色置为黑色
	2、判断父节点的颜色,如果父节点的颜色为黑色
		不用处理,不违反红黑树的性质
	3、父节点是红色,叔叔节点也是红色
		3.1、将父节点和叔叔节点设置为黑色,祖父节点设置为红色
		3.2、以祖父节点为插入节点,在进行判断
	4、父节点是红色,叔叔节点是黑色
		4.1、左左插入
			父节点变成黑色,祖父节点变成红色,祖父节点右旋
		4.2、左右插入
 	    	父节左旋,之后父节点就变成了子节点
	 	 	按照父节点为当前插入的红色节点,进行左左插入处理
		4.3、右右插入
			父节点变成黑色,祖父节点变成红色,祖父节点左旋
		4.4、右左插入
 	    	父节点右旋,之后父节点变成了子节点
 	    	按照父节点为当前插入的红色节点,进行右右插入处理

演示:
在这里插入图片描述
假如需要插入数字3,则查找节点,数字3的初始位置为 数字2 节点的右子树,如下图:
在这里插入图片描述
根据插入的判断条件,从第一条依次向下进行判断,符合4.3:父节点(数字2)是红色,叔叔节点(数字2 的左子节点空节点)为黑色(空节点)条件,右右的情况,父节点(数字2)变成黑色,祖父节点(数字1)变成红色,祖父节点(数字1)左旋,得到最终的结果,如下图:
在这里插入图片描述

2. 红黑树删除节点过程

总结:
	删除节点时,先看删除节点的颜色,再看兄弟节点的颜色,再看侄子节点的颜色(先看远侄子,再看近侄子)
最后看父节点的颜色
	当前节点->兄弟节点->远侄子节点->近侄子节点->父节点
	(补充)删除节点分两种情况,一种是叶子节点,一种不是叶子节点,如果不是叶子节点,进行和子节点值
的交换,直至节点没有叶子节点,变成删除叶子节点
	1、删除的是叶子节点,并且叶子节点为红色
		直接删除,不需要后续处理
	2、删除的是叶子节点,并且叶子节点为黑色
		需要处理
	3、删除节点下有一个子节点
		将当前删除的节点和子节点的值进行交换,就变成了删除叶子节点
		3.1、如果叶子节点是红色,对应情况1,直接删除
		3.2、如果叶子节点是黑色,对应情况2,进行后续处理
	4、删除节点有两个子节点
		将当前节点和后续节点中的一个节点值进行交换,改为删除叶子节点
		4.1、没有叶子节点
			对应情况1、2
		4.2、有一个叶子节点
			对应情况3
		4.2、有两个叶子节点
			对应情况4
	经过上述步骤的转换,将情况转换为删除叶子节点,其中叶子节点为红色的已经处理过,现在只需要考虑,
删除叶子节点为黑色的情况
	5、删除的叶子节点为黑色
		5.1、删除节点的兄弟节点为红色
			删除节点为左节点:
				将父节点和兄弟节点颜色互换,对父节点进行左旋
			删除节点为右节点:
				将父节点和兄弟节点颜色互换,对父节点进行右旋
		5.2、删除节点兄弟节点为黑色,远侄子节点为红色
			删除节点为左节点:
				这时删除节点的远侄子节点为兄弟节点的右节点
				将父节点和兄弟节点颜色对调,并把远侄子节点变成黑色,对父节点进行左旋
				删除当前需要删除的节点
			删除节点为右节点:
				这时删除节点的远侄子节点为兄弟节点的左节点
				将父节点和兄弟节点颜色对调,并把远侄子节点变成黑色,对父节点进行右旋
				删除当前需要删除的节点
		5.3、删除节点兄弟节点是黑色,远侄子节点是红色
			删除节点为左节点:
				近侄子节点和兄弟节点颜色互换,并将近侄子节点进行右旋
				这时候就变成了5.2情况
			删除节点为右节点:
				近侄子节点和兄弟节点颜色互换,将近侄子节点进行左旋
				这时候就变成了5.2情况
		5.4、父节点是红色,兄弟节点和兄弟节点的两个孩子(只能是空节点)都是黑色的情况
			将父节点变成黑色,兄弟节点变成红色
			删除当前节点
		5.5、父节点和兄弟节点及兄弟节点的两个子节点,都是黑色
			将兄弟节点变成红色,删除节点
			这样删除节点后,父节点的左右两个黑色节点数就相等了,但是经过祖父节点的路径黑色节点
			的数就少1个,这个时候,我们再以父节点为起始节点(不用删除节点了),继续根据情况进行
			平衡操作,在看是哪种情况,在进行对应的调整,这样一直向上,直到新的起始节点为根节点


示例:

初始红黑树结构如下图所示:
在这里插入图片描述
假如需要删除数字4,经过判断,满足 5.5、父节点(数字3)和兄弟节点(数字2)及兄弟节点的两个子节点(空节点),都是黑色,将兄弟节点(数字2)变成红色,并删除当前节点(数字4),如下图所示:
在这里插入图片描述
这时以父节点(数字3)为当前节点进行后续操作(不在删除数据),经过判断,满足5.2、删除节点兄弟节点为黑色,远侄子节点为红色,并且删除节点为左节点,将父节点(数字9)和兄弟节点(数字14)颜色对调,并把远侄子节点(数字18)变成黑色,对父节点(数字9)进行左旋,如下图所示:
在这里插入图片描述
至此,删除节点(数字4)流程完毕

3. 红黑树修改&查找节点过程

修改和查找节点过程相同,列举查找过程
1、以根节点进行和查找值进行对比
	1.1、等于当前值
		直接返回
	1.2、小于当前值
		以左节点为当前节点进行对比,重复情况1
	1.3、大于当前值
		以右节点为当前节点进行对比,重复情况1
2、当遍历到叶子节点,而且没有找到对应的值,则返回空
<think>红黑树删除红色节点的过程相对简单,因为删除红色节点不会破坏红黑树的性质(尤其是黑色高度性质)。以下是详细步骤: ### 删除红色节点的过程 1. **定位节点**:首先找到要删除节点。 2. **情况分析**: - **情况1:被删除节点是叶子节点(直接删除)**:如果该红色节点是叶子节点(即两个子节点都是NIL),则直接删除,不会影响任何红黑树性质。 - **情况2:被删除节点有一个子节点**:根据红黑树性质,红色节点不可能只有一个非NIL子节点(因为红色节点的两个子节点必须是黑色,而如果只有一个非NIL子节点,那么该子节点必须是黑色,但这样就会违反性质5,因为从该红色节点到叶子节点的路径中,经过子节点的路径会多一个黑色节点,而另一条路径(NIL)只有红色节点,然后就是黑色叶子节点,所以黑色高度不同)。因此,这种情况实际上不可能发生。 - **情况3:被删除节点有两个子节点**:此时需要找到后继节点(右子的最小节点),用后继节点的值替换当前节点,然后删除后继节点。注意,后继节点要么是红色叶子节点,要么是一个黑色节点(此时需要按黑色节点删除处理)。 因此,真正需要处理的是情况1(叶子节点)和情况3(转化为删除后继节点,而后继节点可能是红色或黑色)。 ### 删除红色叶子节点的步骤 1. **直接删除**:将该节点从其父节点上移除(将父节点对应的指针设为NIL)。 2. **结束**:由于删除的是红色节点,不会影响黑色高度,也不会产生两个相邻的红色节点(因为删除节点是红色,其父节点和子节点都是黑色,删除后父节点指向NIL(黑色),所以性质保持不变)。 ### 代码示例 以下为删除操作中处理红色节点的部分代码(基于之前的红黑树实现): ```cpp bool Delete(const KEY& key) { RBTreeNode<KEY, VALUE>* z = find(key); if (z->isNIL()) return false; // 未找到 RBTreeNode<KEY, VALUE>* y = z; // y指向要删除节点 RBTreeNode<KEY, VALUE>* x = nullptr; // x用于指向可能破坏平衡的节点 COLOR original_color = y->m_Color; // 情况1:被删除节点最多有一个非NIL子节点 if (z->m_pLeft->isNIL()) { x = z->m_pRight; // x可能是NIL(叶子节点)或一个黑色节点(如果z只有一个子节点,则z必须是黑色,且子节点为红色,但这里z是红色,所以不会进入?) transplant(z, z->m_pRight); } else if (z->m_pRight->isNIL()) { x = z->m_pLeft; transplant(z, z->m_pLeft); } else { // 情况2:有两个非NIL子节点,则找到后继节点(右子的最小节点) y = minimum(z->m_pRight); original_color = y->m_Color; x = y->m_pRight; // 注意:后继节点y的左孩子一定是NIL,因为y是最小节点 if (y->m_pParent == z) { // 如果y是z的直接子节点 x->m_pParent = y; // 即使x是NIL,也要设置其父节点(这里NIL的父节点会被设置,但通常我们不使用NIL的父节点) } else { // 用y的右子替换y(y的左子一定为NIL) transplant(y, y->m_pRight); y->m_pRight = z->m_pRight; y->m_pRight->m_pParent = y; } // 用y替换z transplant(z, y); y->m_pLeft = z->m_pLeft; y->m_pLeft->m_pParent = y; y->m_Color = z->m_Color; // 保持z的颜色 } delete z; // 如果删除的是黑色节点,才需要调整(因为可能破坏性质5) if (original_color == COLOR::BLACK) { delete_fixup(x); } return true; } ``` ### 说明 - 当被删除节点`z`是红色时,`original_color`为`RED`,因此不会进入`delete_fixup`函数。 - 如果`z`是红色叶子节点,则直接进入第一个分支(左子节点为NIL,右子节点为NIL,所以x指向NIL),然后调用`transplant`将父节点指向该节点的指针改为指向NIL(黑色节点),删除结束。 - 如果`z`是红色节点且有两个子节点,那么我们会找到其后继节点`y`(注意:`y`一定是黑色?不一定,后继节点可能是红色吗?)。实际上,后继节点`y`可能是红色,也可能是黑色。但是,如果`y`是红色,那么它一定是叶子节点(因为如果它有左子节点,那就不是最小节点;而它作为后继节点,右子节点可能是NIL或非NIL,但如果是非NIL,那么最小节点应该是它的左子中,所以`y`一定没有左子节点,而右子节点可能是NIL或非NIL?实际上,后继节点是右子中的最小节点,所以它一定没有左子节点,但可能有右子节点。然而,在红黑树中,如果`y`是红色,那么它的两个子节点必须是黑色(NIL节点)。所以,如果`y`是红色,那么它一定是叶子节点(两个子节点都是NIL)。因此,当删除`y`时,因为`y`是红色,所以不会触发修正。 ### 重要注意点 - 在删除有两个子节点节点时,我们实际上删除的是后继节点`y`(将`y`移动到`z`的位置,然后删除`y`)。因此,如果`y`是红色,那么删除`y`不会引起调整;如果`y`是黑色,则需要进行调整(通过`delete_fixup`)。 - 在代码中,我们记录的是后继节点`y`的原始颜色(`original_color`),如果这个颜色是黑色,那么删除后就需要调整。 ### 总结 删除红色节点时,如果该节点是叶子节点,直接删除即可;如果该节点有两个子节点,则实际上删除的是其后继节点(可能是红色也可能是黑色)。如果是删除后继节点且后继节点为红色,则直接删除后继节点(叶子节点)不会破坏红黑树性质。 §§相关问题§§: 1. 红黑树删除黑色叶子节点的详细过程是怎样的? 2. 在删除有两个子节点节点时,为什么选择后继节点而不是前驱节点? 3. 如何处理删除操作中出现的“双黑”问题? 4. 红黑树删除操作的最坏时间复杂度是多少?</think>### 红黑树删除红色节点的详细过程 在红黑树删除红色节点相对简单,因为不会破坏红黑树的核心性质(特别是黑色高度性质)。以下是详细的删除过程和代码实现: #### 删除红色节点的关键步骤 1. **定位节点**: - 找到要删除的目标节点 - 确认节点颜色为红色 2. **删除操作**: - 情况1:叶子节点(无子节点) - 直接删除 - 情况2:有一个子节点 - 子节点替换 - 情况3:有两个子节点 - 用后继节点替换 3. **性质维护**: - 红色节点删除不会改变黑色高度 - 不会产生连续红色节点问题 - 不需要旋转或颜色调整 #### 代码实现(C++) ```cpp bool Delete(const KEY& key) { // 查找目标节点 RBTreeNode<KEY, VALUE>* z = find(key); if (z->isNIL()) return false; // 节点不存在 // 记录原始颜色(红色) COLOR originalColor = z->m_Color; // 情况1:叶子节点(无子节点) if (z->m_pLeft->isNIL() && z->m_pRight->isNIL()) { // 直接解除父节点链接 if (z->m_beLong == BELONG::LEFT) { z->m_pParent->m_pLeft = &NIL; } else if (z->m_beLong == BELONG::RIGHT) { z->m_pParent->m_pRight = &NIL; } // 如果是根节点 if (z == m_pRoot) { m_pRoot = &NIL; } delete z; return true; } // 情况2:只有一个子节点 if (z->m_pLeft->isNIL() || z->m_pRight->isNIL()) { RBTreeNode<KEY, VALUE>* child = z->m_pLeft->isNIL() ? z->m_pRight : z->m_pLeft; // 子节点替换当前节点 transplant(z, child); // 确保子节点为黑色(红黑树性质) child->m_Color = COLOR::BLACK; delete z; return true; } // 情况3:有两个子节点 // 找到后继节点(右子的最小节点) RBTreeNode<KEY, VALUE>* successor = minimum(z->m_pRight); // 复制后继节点数据到当前节点 z->m_Key = successor->m_Key; z->m_Value = successor->m_Value; // 递归删除后继节点(可能是红色或黑色) return Delete(successor->m_Key); } // 节点替换辅助函数 void transplant(RBTreeNode<KEY, VALUE>* u, RBTreeNode<KEY, VALUE>* v) { if (u->m_pParent->isNIL()) { m_pRoot = v; v->m_beLong = BELONG::ROOT; } else if (u->m_beLong == BELONG::LEFT) { u->m_pParent->m_pLeft = v; } else { u->m_pParent->m_pRight = v; } v->m_pParent = u->m_pParent; v->m_beLong = u->m_beLong; } ``` #### 三种情况的详细说明 1. **叶子节点(无子节点)**: ```mermaid graph TD A[父节点] --> B[红色节点] B --> NIL1[NIL] B --> NIL2[NIL] 删除后: A --> NIL1 ``` - 直接解除父节点链接 - 父节点指向NIL节点 - 删除节点,无需额外操作 2. **只有一个子节点**: ```mermaid graph TD A[父节点] --> B[红色节点] B --> C[黑色子节点] B --> NIL[NIL] 删除后: A --> C ``` - 用子节点替换当前节点 - 确保子节点变为黑色(维护性质4) - 不需要旋转或颜色调整 3. **有两个子节点**: ```mermaid graph TD A[红色节点] --> B[左子] A --> C[右子] 找到C的最小节点D 用D的值替换A 递归删除D ``` - 找到右子的最小节点(后继节点) - 复制后继节点的数据到当前节点 - 递归删除后继节点(可能是红色或黑色) #### 为什么红色节点删除简单? 1. **不改变黑色高度**: - 删除红色节点不会减少任何路径的黑色节点数 - 性质5(所有路径黑色节点数相同)保持不变 2. **不产生连续红色**: - 红色节点的父节点和子节点都是黑色 - 删除后不会产生连续红色节点 3. **子结构不变**: - 删除操作不改变子的高度平衡 - 不需要旋转调整结构 #### 特殊情况处理 当删除的红色节点是根节点时: ```cpp // 在叶子节点情况中处理 if (z == m_pRoot) { m_pRoot = &NIL; // 变为空 } ``` 当只有一个子节点时强制变黑: ```cpp child->m_Color = COLOR::BLACK; // 维护性质4 ``` ### 删除操作时间复杂度 | 操作 | 时间复杂度 | 说明 | |------|------------|------| | 查找节点 | O(log n) | 标准二叉搜索查找 | | 叶子节点删除 | O(1) | 直接解除链接 | | 单子节点删除 | O(1) | 简单替换 | | 双子节点处理 | O(log n) | 查找后继节点 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值