C++ 手撕红黑树(二):删除操作

C++ 手撕红黑树(二):删除操作


前情概述:C++ 手撕红黑树(一):节点的插入

1.红黑树的删除概述

在我们前面的文章中,归纳了红黑树的删除一个节点的情况,总结起来就是:删除看叔叔节点。在红黑树的插入过程中,也有这样的规律,那就是:删除看兄弟。我们只需要判断兄弟节点的颜色,就可以区分各种情况!

2.红黑树的删除

在进行情况分类之前,我们首先考虑最简单的情况:什么情况下我们删除一个节点,无需任何操作。根据前面已经提到的红黑树性质,每条路径的黑色节点数在删除前后应该是保持一致。所以,我们先出来一个结论:当删除的节点是红色的时候时,才不需要任何后续的操作。因此,这是一种简单的情况。下面我们开始讨论删除的节点是黑色的情况。

2.1 删除的节点是黑色

当需要删除的节点颜色是黑色时,最基本的情况如下所示:
在这里插入图片描述
其中,白色的节点表示不确定颜色。那么,既然不确定颜色,我们就来给它上色,然后分类讨论。

首先,我们在左边的子树删除了一个节点1,那么以1为根的接下来的路径黑色的数量都会少1。既然少了1,那我们就补回来。从哪儿补?很明显,兄弟的节点值都要大于1,肯定不是以兄弟(值为8的节点)为根的子树拿一个节点出来补,那就拿父节点来补。拿父节点来补,就是旋转。所以,在下面的分类讨论中,都涉及到了旋转。

首先,删除看兄弟,我们按兄弟的颜色进行区分,可以分为以下的四种情况:

情况1,兄弟是黑色的,而且有一个红色的右孩子:
在这里插入图片描述
如果我们要把删除的1的所在路径补上一个黑色,那就只能把父亲5左旋下来。然后为了保持这个树的局部性质不变,我们交换待删除节点的原兄弟和原父亲的颜色,并把兄弟的右孩子置为黑色(图中白色为未知颜色):
在这里插入图片描述
这样,第一种情况就完成了,而且不必再继续调整。

情况2,兄弟是黑色的,但是右孩子是黑色的,而左孩子是红色的:
在这里插入图片描述
在这种情况下,我们尽可能想向情况1靠拢,怎么办呢?右旋兄弟就行了:
在这里插入图片描述
这个时候已经跟1很像了,我们再交换6和8的颜色就一样了:
在这里插入图片描述
可以验证,从5到1、6、9的黑色节点个数在上面的操作之后没有任何变化。后续再按情况1进行调整即可

在上面的两种情况中,兄弟的孩子都有一个红色的,如果没有怎么办?因此,有了情况三:

情况三:兄弟的孩子都是黑色的
在这里插入图片描述
在之前,我们都是借兄弟黑色节点(兄弟有一个红色孩子,通过涂黑来补兄弟的位置)。现在兄弟也没了,那怎么办?没辙了,兄弟和我(待删除的黑色)那就一起减一吧,所以我们把兄弟变成了红色:
在这里插入图片描述
但是这样做是危险的,如果父节点5是红色怎么办?没得办,我们只能迭代检查了。如果父节点5原本是红色,那就太好啦,我们把5涂成黑色,那我跟兄弟减的1不是又加回来了?如果不是红色,我们继续向上检查,直到遇见一个节点是红色的,把它涂成黑色,就完成了我们所有的操作。那没有红色怎么办,其实在回溯的过程中,没有红色,就落在了我们正在讨论的情况之一(包括下面要介绍的情况4)。至此,我们的情况3已经讨论完毕了。
在这里插入图片描述
在上面的情况中,我们都是讨论了兄弟是黑色的情况,如果兄弟是红色的怎么办?注意,如果兄弟是红色,那父节点就一定是黑色的,所以局部情况长这个样子:
在这里插入图片描述
很明显,当兄弟是红色,只有这一种情况。要把这个节点补回来,我们还是选择向兄弟借!现在兄弟是红色的,我们又有机可乘了。先尝试把父节点5旋下来补这个黑色:
在这里插入图片描述
我们可以分析一下,在旋转之前,5到9有两个黑色,现在8到9只剩下一个了。那怎么办?先把8涂黑!:
在这里插入图片描述
再检查一下,发现原来5到6只有两个黑色,现在变成了三个!那怎么办?总不能把8涂回来吧?只能涂5了,把5涂成红色:
在这里插入图片描述
现在检查一下8到1、6、9和2到1、6、9的黑色节点有没有一样。结果确实是一样的。再看上面的情况,待删除节点的兄弟变成6了,6是什么颜色?黑色!那就是说,情况四经过我们的处理,又变成了上面三种情况的任意一种!

总结一下

在上面中,我们根据兄弟的颜色,分了四种情况,其中三种是兄弟是黑色,一种是兄弟是红色。情况4经过处理,可以变成情况1~3的一种;情况3处理之后,又变成1、2、4的一种;情况2经过处理会变成情况1。
因此在处理的过程中,我们可以先看看是不是4,是的话处理一下;再看看是不3,是的话处理一下;再看看是不是2,是的话处理一下;最后看是不是1,是的话处理一下。

注意:由于情况3需要一直迭代处理,当遇见红色节点就可以退出了;而4是1~3的其中一种,所以我们需要一个循环,来迭代处理。而且,我们需要把1、2、3、4四种情况都写在这个循环内,即便是只需要在局部的树中处理就完成的情况1、2也是如此。为什么?因此情况4可能会变成1、2啊。难不成我们要1、2写一个单独的函数,等4变成1的时候就调用1的函数,变成2的时候调用2的函数?这样代码冗余度太高了!那不如全部放在一起得了。循环终止条件是什么?我们采取情况3得跳出情况来作为终止条件,就是当前的节点是红色时,跳出来,把当前得节点染成红色就行了。那么,本来只需要执行一次的情况1、2,现在会不会执行了很多次?不会!当是情况1或者2的时候,我们直接跳出循环!分析完成!开始上代码!

3.删除节点实现

// 删除
	void remove(const int& val) {
		if (root_ == nullptr) {
			return;
		}

		Node* cur = root_;
		while (nullptr != cur) {
			if (cur->val_ > val) {
				cur = cur->left_;
			} else if (cur->val_ < val) {
				cur = cur->right_;
			} else {
				break;
			}
		}

		if (nullptr == cur) {
			// 没找到 直接返回
			return;
		} 

		// 1.如果待删除的节点有左右孩子的话,我们就把前驱节点覆盖到待删除节点
		// 然后去删除前驱节点
		if (nullptr != cur->left_ && nullptr != cur->right_) {
			Node* pre = cur->left_;
			while (nullptr != pre->right_) {
				pre = pre->right_;
			}
			// 在下面删除前驱节点
			cur->val_ = pre->val_;
			cur = pre;
		}

		// 2.到这里的话 最多只有一个孩子
		Node* child = cur->left_;
		if (cur->right_) {
			child = cur->right_;
		}
		// 如果确实有一个孩子
		if (nullptr != child) {
			child->parent_ = cur->parent_;
			if (cur->parent_ == nullptr) {
				// 说明待删除节点是根节点
				root_ = child;
				/*setColor(root_, BLACK);
				return;*/
			} else {
				if (cur->parent_->left_ == cur) {
					// 说明cur是其父亲的左孩子
					cur->parent_->left_ = child;
				} else {
					// 说明cur是其父亲的右孩子
					cur->parent_->right_ = child;
				}
			}

			Color deleteColor = cur->color_;
			delete cur;
			// 删除的节点是黑色节点才要检查
			if (BLACK == deleteColor) {
				fixAfterRemove(child);
			}
		} else {
			// child = nullptr
			// 删除的节点是叶子节点
			if (nullptr == cur->parent_) {
				// 而且是根节点
				delete cur;
				root_ = nullptr;
				return;
			} else {
				if (color(cur) == BLACK) {
					// 先调整,再删除 ???
					fixAfterRemove(cur);
				} 

				if (cur->parent_->left_ == cur) {
					cur->parent_->left_ = nullptr;
				} else {
					cur->parent_->right_ = nullptr;
				}
				delete cur;
			}
		}
	}

	// 开始检查
	void fixAfterRemove(Node* node) {
		while (node != root_ && color(node) == BLACK) {
			if (left(parent(node)) == node) {
				// 待删除的黑色节点在左子树
				Node* brother = right(parent(node));
				if (color(brother) == RED) {
					// 情况4
					setColor(parent(node), RED);	// 把父亲染红
					setColor(brother, BLACK);		// 把兄弟染黑
					leftRoate(parent(node));		// 左旋父节点
					brother = right(parent(node));	// 重置兄弟
				} 
				if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
					// 情况3  
					setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
					node = parent(node);	// 迭代检查
				} else {
					// 情况1、2 进入这里 执行完就会退出
					if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
						// 情况2
				/*		rightRoate(brother);
						brother = right(parent(node));
						setColor(brother, BLACK);
						setColor(right(brother), RED);*/

						setColor(brother, RED);
						setColor(left(brother), BLACK);
						rightRoate(brother);
						brother = right(parent(node));
					}

					//情况1
					setColor(brother, color(parent(node)));
					setColor(parent(node), BLACK);
					setColor(right(brother), BLACK);
					leftRoate(parent(node));
					break;
				}
			} else {
				// 待删除的黑色节点在右子树 把上面涉及方向的操作都反一下就行
 				Node* brother = left(parent(node));
				if (color(brother) == RED) {
					// 情况4
					setColor(parent(node), RED);	// 把父亲染红
					setColor(brother, BLACK);		// 把兄弟染黑
					rightRoate(parent(node));		// 左旋父节点
					brother = left(parent(node));	// 重置兄弟
				}
				if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
					// 情况3  
					setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
					node = parent(node);	// 迭代检查
				} else {
					// 情况1、2 进入这里 执行完就会退出
					if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
						// 情况2
						/*leftRoate(brother);
						brother = left(parent(node));
						setColor(brother, BLACK);
						setColor(left(brother), RED);*/

						setColor(brother, RED);
						setColor(right(brother), BLACK);
						leftRoate(brother);
						brother = left(parent(node));
					}

					//情况1
					setColor(brother, color(parent(node)));
					setColor(parent(node), BLACK);
					setColor(left(brother), BLACK);
					rightRoate(parent(node));
					
					break;
				}
			}
		}

		// 如果发现node指向的节点是红色,直接涂成黑色,调整结束
		setColor(node, BLACK);
	}

3. 整棵红黑树的实现

#include <iostream>
#include <queue>

enum Color {
	BLACK,
	RED
};

struct Node
{
	int val_;
	Node* left_;
	Node* right_;
	Node* parent_;
	Color color_;
	Node(int val = INT_MAX, Node* left = nullptr, Node* right = nullptr,
		Node* parent = nullptr, Color color = BLACK) :
		val_(val), left_(left), right_(right), parent_(parent), color_(color) {
	};
};

class RB_tree {	

public:
	RB_tree() : root_(nullptr) {}

	// 返回节点的颜色
	Color color(Node* node) {
		return nullptr == node ? BLACK : node->color_;
	}
	// 返回节点的左孩子
	Node* left(Node* node) {
		return node->left_;
	}
	// 返回节点的右孩子
	Node* right(Node* node) {
		return node->right_;
	}
	// 返回节点的父节点
	Node* parent(Node* node) {
		return node->parent_;
	}
	// 设置颜色
	void setColor(Node* node, Color color) {
		node->color_ = color;
	}
	// 左旋
	void leftRoate(Node* node) {
		Node* child = node->right_;
		child->parent_ = node->parent_;
		if (node->parent_ == nullptr) { 
			// node本身是根节点 子节点转上去就会变成根节点
			root_ = child;
		} else {
			if (node->parent_->left_ == node) {
				// node是父节点的左孩子
				node->parent_->left_ = child;
			} else {
				// node是父节点的右孩子
				node->parent_->right_ = child;
			}
		}

		node->right_ = child->left_;
		if (child->left_ != nullptr) {
			child->left_->parent_ = node;
		}

		child->left_ = node;
		node->parent_ = child;
	}
	// 右旋操作
	void rightRoate(Node* node) {
		Node* child = node->left_;
		child->parent_ = node->parent_;
		if (node->parent_ == nullptr) {
			// node是根节点
			root_ = child;
		} else {
			if (node->parent_->left_ == node) {
				node->parent_->left_ = child;
			} else {
				node->parent_->right_ = child;
			}
		}

		node->left_ = child->right_;
		if (child->right_ != nullptr) {
			child->right_->parent_ = node;
		}

		child->right_ = node;
		node->parent_ = child;
	}
	void insert(const int& val) {
		// 如果为空树的话 new一个结点出来即可
		if (root_ == nullptr) {
			root_ = new Node(val);
			return;
		}
		//std::cout << val << std::endl;

		Node* parent = nullptr;
		Node* cur = root_;
		while (cur != nullptr) {
			if (cur->val_ > val) {
				parent = cur;
				cur = cur->left_;
			} else if (cur->val_ < val) {
				parent = cur;
				cur = cur->right_;
			} else {
				// 树里面已经有该值了 直接返回即可
				return;
			}
		}

		Node* newNode = new Node(val, nullptr, nullptr, parent, RED);
		if (parent->val_ > val) {
			parent->left_ = newNode;
		} else {
			parent->right_ = newNode;
		}
		// 插入的节点一定是红色
		// 如果父节点的颜色是红色,那么在插入之后需要进行调整
		if (RED == color(parent)) {
			fixAfterInsert(newNode);
		}
	}

	void fixAfterInsert(Node* node) {
		// 判断是否需要迭代检查
		while (RED == color(parent(node))) {
			// 如果插入节点node的父亲是爷爷节点的左孩子
			if (left(parent(parent(node))) == node) {
				// 那么叔叔节点就是爷爷节点的右孩子
				Node* uncle = right(parent(parent(node)));
				if (RED == color(uncle)) { // 情况1和情况3:叔叔节点是红色的
					setColor(parent(node), BLACK);
					setColor(parent(parent(node)), RED);
					setColor(uncle, BLACK);
					node = parent(parent(node)); // 检查节点从插入节点->爷爷节点(因为爷爷的父亲可能是红色)
				} else {
					// 先判断是不是情况三 如果是的话 把情况三调整为情况2 再后续处理
					if (right(parent(node)) == node) {
						leftRoate(parent(node)); // 对插入节点node的父左旋,掰到同一直线
					}

					// 处理情况2
					setColor(parent(node), BLACK);
					setColor(parent(parent(node)), RED);
					rightRoate(parent(parent(node)));
				}
			} else { // 如果插入节点node的父亲是爷爷节点的右孩子,处理完全镜像,所有left改成right即可
				// 那么叔叔节点就是爷爷节点的右孩子
				Node* uncle = left(parent(parent(node)));
				if (RED == color(uncle)) { // 情况1和情况3:叔叔节点是红色的
					setColor(parent(node), BLACK);
					setColor(parent(parent(node)), RED);
					setColor(uncle, BLACK);
					node = parent(parent(node)); // 检查节点从插入节点->爷爷节点(因为爷爷的父亲可能是红色)
				} else {
					// 先判断是不是情况三 如果是的话 把情况三调整为情况2 再后续处理
					if (left(parent(node)) == node) {
						rightRoate(parent(node)); // 对插入节点node的父左旋,掰到同一直线
					}

					// 处理情况2
					setColor(parent(node), BLACK);
					setColor(parent(parent(node)), RED);
					leftRoate(parent(parent(node)));
				}
			}
		}

		setColor(root_, BLACK); // 强制设置根节点为黑色
	}
	
	// 删除
	void remove(const int& val) {
		if (root_ == nullptr) {
			return;
		}

		Node* cur = root_;
		while (nullptr != cur) {
			if (cur->val_ > val) {
				cur = cur->left_;
			} else if (cur->val_ < val) {
				cur = cur->right_;
			} else {
				break;
			}
		}

		if (nullptr == cur) {
			// 没找到 直接返回
			return;
		} 

		// 1.如果待删除的节点有左右孩子的话,我们就把前驱节点覆盖到待删除节点
		// 然后去删除前驱节点
		if (nullptr != cur->left_ && nullptr != cur->right_) {
			Node* pre = cur->left_;
			while (nullptr != pre->right_) {
				pre = pre->right_;
			}
			// 在下面删除前驱节点
			cur->val_ = pre->val_;
			cur = pre;
		}

		// 2.到这里的话 最多只有一个孩子
		Node* child = cur->left_;
		if (cur->right_) {
			child = cur->right_;
		}
		// 如果确实有一个孩子
		if (nullptr != child) {
			child->parent_ = cur->parent_;
			if (cur->parent_ == nullptr) {
				// 说明待删除节点是根节点
				root_ = child;
				/*setColor(root_, BLACK);
				return;*/
			} else {
				if (cur->parent_->left_ == cur) {
					// 说明cur是其父亲的左孩子
					cur->parent_->left_ = child;
				} else {
					// 说明cur是其父亲的右孩子
					cur->parent_->right_ = child;
				}
			}

			Color deleteColor = cur->color_;
			delete cur;
			// 删除的节点是黑色节点才要检查
			if (BLACK == deleteColor) {
				fixAfterRemove(child);
			}
		} else {
			// child = nullptr
			// 删除的节点是叶子节点
			if (nullptr == cur->parent_) {
				// 而且是根节点
				delete cur;
				root_ = nullptr;
				return;
			} else {
				if (color(cur) == BLACK) {
					// 先调整,再删除 ???
					fixAfterRemove(cur);
				} 

				if (cur->parent_->left_ == cur) {
					cur->parent_->left_ = nullptr;
				} else {
					cur->parent_->right_ = nullptr;
				}
				delete cur;
			}
		}
	}

	// 开始检查
	void fixAfterRemove(Node* node) {
		while (node != root_ && color(node) == BLACK) {
			if (left(parent(node)) == node) {
				// 待删除的黑色节点在左子树
				Node* brother = right(parent(node));
				if (color(brother) == RED) {
					// 情况4
					setColor(parent(node), RED);	// 把父亲染红
					setColor(brother, BLACK);		// 把兄弟染黑
					leftRoate(parent(node));		// 左旋父节点
					brother = right(parent(node));	// 重置兄弟
				} 
				if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
					// 情况3  
					setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
					node = parent(node);	// 迭代检查
				} else {
					// 情况1、2 进入这里 执行完就会退出
					if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
						// 情况2
				/*		rightRoate(brother);
						brother = right(parent(node));
						setColor(brother, BLACK);
						setColor(right(brother), RED);*/

						setColor(brother, RED);
						setColor(left(brother), BLACK);
						rightRoate(brother);
						brother = right(parent(node));
					}

					//情况1
					setColor(brother, color(parent(node)));
					setColor(parent(node), BLACK);
					setColor(right(brother), BLACK);
					leftRoate(parent(node));
					break;
				}
			} else {
				// 待删除的黑色节点在右子树 把上面涉及方向的操作都反一下就行
 				Node* brother = left(parent(node));
				if (color(brother) == RED) {
					// 情况4
					setColor(parent(node), RED);	// 把父亲染红
					setColor(brother, BLACK);		// 把兄弟染黑
					rightRoate(parent(node));		// 左旋父节点
					brother = left(parent(node));	// 重置兄弟
				}
				if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
					// 情况3  
					setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
					node = parent(node);	// 迭代检查
				} else {
					// 情况1、2 进入这里 执行完就会退出
					if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
						// 情况2
						/*leftRoate(brother);
						brother = left(parent(node));
						setColor(brother, BLACK);
						setColor(left(brother), RED);*/

						setColor(brother, RED);
						setColor(right(brother), BLACK);
						leftRoate(brother);
						brother = left(parent(node));
					}

					//情况1
					setColor(brother, color(parent(node)));
					setColor(parent(node), BLACK);
					setColor(left(brother), BLACK);
					rightRoate(parent(node));
					
					break;
				}
			}
		}

		// 如果发现node指向的节点是红色,直接涂成黑色,调整结束
		setColor(node, BLACK);
	}

	// 返回根节点
	Node* getRoot() {
		return root_;
	}

private:
	Node* root_;
};


void printTree(Node* root) {
	std::queue<Node*> que;
	if (root) que.push(root);

	while (!que.empty()) {
		int size = que.size();

		for (int i = 0; i < size; ++i) {
			Node* cur = que.front(); que.pop();
			if (cur != nullptr) {
				std::cout << cur->val_ << "(" << (cur->color_ == 0 ? "B" : "R") << ")" << " ";
				que.push(cur->left_);
				que.push(cur->right_);
			} else {
				std::cout << "NULL ";
			}
		}
		std::cout << std::endl;
	}
}

int main() {
	
	{   // 测试删除操作
		std::cout << "==========插入============" << std::endl;
		RB_tree rbTree;
		for (int i = 1; i <= 10; ++i) {
			rbTree.insert(i);
		}
		printTree(rbTree.getRoot());

		std::cout << "==========删除============" << std::endl;
		rbTree.remove(9);
		rbTree.remove(10);
		printTree(rbTree.getRoot());
	}
	return 0;
}

5.测试结果

可以验证,结果完全正确:

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值