红黑树

红黑树

1. 红黑树定义

红黑树是一种自平衡二叉搜索树,每个节点增加一个存储位表示颜色(红/黑)。通过对任何一条从根到叶子的路径上节点颜色的约束,确保没有一条路径比其他路径长出2倍,因此是近似平衡的。

2. 五大性质

  1. 每个节点是红色或黑色

  2. 根节点是黑色

  3. 所有叶子节点(NIL)是黑色 —— 这里的叶子指空节点

  4. 不能有两个连续的红节点

  5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点 —— 黑高一致

3. 黑高定义

  • 从某个节点(不含自身)到其叶子节点路径上的黑色节点数

  • 根的黑高 = 树的黑高

4. 平衡性保证

  • 最长路径(红黑交替) ≤ 2 × 最短路径(全黑)

  • 操作时间复杂度:查找、插入、删除均为 O(log n)

5.插入操作

对于要插入的数据,默认已经通过二叉搜索树的性质找到了插入位置。

首先判断是否为根节点,若是,则染黑并结束插入;若不是则染为红色并判断是否满足“不红红”?满足则直接插入;不满足则需要调整平衡。

调整平衡:对于导致失衡的结点,判断其叔叔结点的颜色,若为黑色则需要根据对应情况进行旋转操作(LL,RR,LR,RL)结束插入;若为红色则将叔叔,父亲,爷爷结点染为各自相反的颜色,并将爷爷结点作为新的节点从头开始判断是否为根节点,一直到结束插入为止。

红黑树插入操作

为什么每次插入的新结点需要先染为红色?
在每次插入数据时,若插入节点为黑色,那么不仅会破坏“黑高一致”这个特性,还可能破坏“最长路径(红黑交替) ≤ 2 × 最短路径(全黑)”这一基本平衡条件;但若插入节点为红色,那么只有可能破坏“不红红”这一特性,其余性质都不会被破坏,只需围绕这一特性进行调整即可。

插入操作:

void insertNode(const int& val) {
		Node* node = new Node(val);
		node->left = node->right = NIL;

		Node* cur = root;
		Node* prev = nullptr;

		while (cur != NIL) {
			prev = cur;//prev用于标记父亲位置
			if (cur->val < node->val) {
				cur = cur->right;
			}
			else if (node->val < cur->val) {
				cur = cur->left;
			}
			else {
				delete node;//不插重复数
				return;
			}
		}
		node->parent = prev;
		if (prev == nullptr) {
			root = node;
		}
		else if (prev->val > node->val) {
			prev->left = node;
		}
		else {
			prev->right = node;
		}
		insertFixup(node);
	}
};

该操作成功将新结点node添加到对应位置,满足二叉搜索树的性质,最后通过insertFixup(node)进行平衡调整。

插入修复操作:

void insertFixup(Node* node) {
	while (node->parent && node->parent->color == RED) {//违反“不红红”需要调整
		if (node->parent == node->parent->parent->left) {//父节点在爷节点左侧,则叔节点在右侧
			Node* uncle = node->parent->parent->right;//叔叔节点
			//得到叔结点,开始判断颜色;
			if (uncle->color == RED) {
				//case 1:叔为红色,则叔父爷三个节点染色,爷节点作为新结点
				node->parent->color = BLACK;//新结点违反“不红红”,父节点一定红,变黑
				node->parent->parent->color = RED;//已知叔为红,那么爷一定黑,所以变红
				uncle->color = BLACK;
				node = node->parent->parent;//爷节点作为新结点继续调整
			}
			else {//叔叔为黑,则判断相对位置进行旋转并将叔父爷染色;(叔原本为黑所以不写)
				//case 2:node为右孩子,那么是LR旋转,先进行左旋,再右旋
				if (node == node->parent->right) {
					node = node->parent;
					leftRotate(node);
				}//此时node处于最左下角

				//case 3:node为左孩子,是LL旋转,直接右旋并染色即可
				node->parent->color = BLACK;
				node->parent->parent->color = RED;
				rightRotate(node->parent->parent);
				//此操作是以父节点为轴,将爷节点拉到父节点的右侧,与node成为兄弟节点
			}
		}
		else if(node->parent == node->parent->parent->right){//对称情况,叔结点在左侧
			Node* uncle = node->parent->parent->left;//叔结点
			if (uncle->color == RED) {
				node->parent->color = BLACK;
				node->parent->parent->color = RED;
				uncle->color = BLACK;
				node = node->parent->parent;
			}
			else {//叔为黑,判断相对位置
				if (node == node->parent->left) {
					node = node->parent;
					rightRotate(node);//先右旋
				}
				node->parent->color = BLACK;
				node->parent->parent->color = RED;
				leftRotate(node->parent->parent);
			}
		}
	}
	root->color = BLACK;
}

旋转操作:

//需要将node移到其左孩子的右侧,即绕着左孩子顺时针旋转(左孩子必须存在才能操作)
void rightRotate(Node* node) {
	//child 是node左孩子,操作完成后child将取代node位置,node在child右侧
	Node* child = node->left;

	//将child的右子树挂在node的左子树上
	//原因:node在操作完后将处于child的右子树,若child右子树不空则会冲突,提前处理右子树
	node->left = child->right;

	if (child->right != NIL) {
		child->right->parent = node;
	}
	//处理child的父节点
	child->parent = node->parent;

	if (node->parent == nullptr) {
		root = child;
	}//将child连在node的父节点下面
	else if (node == node->parent->left) {//判断node为哪个孩子
		node->parent->left = child;
	}
	else if (node == node->parent->right) {
		node->parent->right = child;
	}
	node->parent = child;
	child->right = node;
}
//需要将node移到其右孩子的左侧,即绕着右孩子逆时针旋转(右孩子必须存在才能操作)
void leftRotate(Node* node) {
	Node* child = node->right;
	node->right = child->left;
	if (child->left != NIL) {
		child->left->parent = node;
	}
	child->parent = node->parent;
	if (node->parent == nullptr) {
		root = child;
	}
	else if (node == node->parent->left) {
		node->parent->left = child;
	}
	else if (node == node->parent->right) {
		node->parent->right = child;
	}
	child->left = node;
	node->parent = child;
}

7.删除操作

void deleteNode(Node* node) {
	Node* realdeletedNode = node;//实际要删除的节点
	Color deletedcolor = realdeletedNode->color;//实际要删除的节点的颜色
	Node* replaceNode = nullptr;//最终要替换删除节点位置的节点
	if (node->left == NIL) {
		replaceNode = node->right;
		transplant(realdeletedNode, realdeletedNode->right);
	}
	else if (node->right == NIL) {
		replaceNode = node->left;
		transplant(realdeletedNode, realdeletedNode->left);
	}
	else {//有两个孩子节点, 则需找到直接后继节点代替原来要删除节点的位置,删除后继节点
		realdeletedNode = findminNode(node->right);//直接后继节点
		deletedcolor = realdeletedNode->color;//直接后继节点的颜色
		//直接后继节点的右孩子(用于替换待删除节点位置)
		//为什么是right?因为直接后继节点只可能是没有叶节点和只有右孩子两种情况
		replaceNode = realdeletedNode->right;
		
		//接下来需要让直接后继节点接管node的右子树,以保证二叉树的性质不变
		//处理两种情况:1.直接后继节点本身就在node的右侧 2.直接后继节点在更深处
		//case 1:就在node右侧,已经接管了右子树,只需要处理 替换节点 的父指针;
		if (realdeletedNode->parent == node) {
			if (replaceNode != NIL) {
				replaceNode->parent = realdeletedNode;
			}
		}
		else {//case 2:在更深处
			transplant(realdeletedNode, realdeletedNode->right);//从原位置移出
			realdeletedNode->right = node->right;
			realdeletedNode->right->parent = realdeletedNode;
		}
		transplant(node, realdeletedNode);//用后继节点替换node位置
		realdeletedNode->left = node->left;//将node的左子树连接
		realdeletedNode->left->parent = realdeletedNode;
		//至此,node已经被彻底隔离,可直接删掉

		realdeletedNode->color = node->color;//替换node的颜色;
	}
	if (deletedcolor == BLACK) {//删除的是黑节点,需要调整
		deleteFixup(replaceNode);
	}
	delete node;
}

删除修复:

//删除修复操作
//参数 currentNode: 替换了被删节点位置的节点
void deleteFixup(Node* currentNode) {
	// 循环直到 currentNode 为根节点,或者 currentNode 变成红色(红色可以直接染黑解决)
	while (currentNode != root && currentNode->color == BLACK) {

		//此时节点为黑色,判断其兄弟节点的颜色情况,而兄弟则分左右两种情况;
		//情况A:兄弟在右边(currentNode是左孩子)
		if (currentNode == currentNode->parent->left) {
			Node* bro = currentNode->parent->right;//兄弟节点

			//判断兄弟节点的颜色
			
			// Case 1: 兄弟节点是红色
			//为红色则父兄变色,将父节点按照currentNode的方向旋转
			if (bro->color == RED) {
				bro->color = BLACK;
				bro->parent->color = RED;
				leftRotate(bro->parent);
				bro = currentNode->parent->right;
			}
			//case 2:兄弟节点两个孩子都是黑的
			if (bro->left->color == BLACK && bro->right->color == BLACK) {
				bro->color = RED;
				currentNode = currentNode->parent;
			}
			else {
				// Case 3: 兄弟节点的右孩子是黑色,左孩子是红色
				// 处理:将左孩子变黑,兄弟变红,右旋兄弟,重新获取兄弟节点
				if (bro->right->color == BLACK) {
					bro->left->color = BLACK;
					bro->color = RED;
					rightRotate(bro);
					bro = currentNode->parent->right;
				}
				// Case 4: 兄弟节点的右孩子是红色(左孩子任意)
				// 处理:兄弟继承父节点颜色,父节点变黑,右孩子变黑,左旋父节点
				// 此时红黑树性质完全恢复,将 currentNode 指向根以退出循环
				bro->color = bro->parent->color;
				bro->parent->color = BLACK;
				bro->right->color = BLACK;
				leftRotate(bro->parent);
				currentNode = root;
			}
		}
		//情况B:兄弟在左边;以下为对称情况
		else if (currentNode == currentNode->parent->right) {
			Node* bro = currentNode->parent->left;
			if (bro->color == RED) {
				bro->color = BLACK;
				bro->parent->color = RED;
				rightRotate(bro->parent);
				bro = currentNode->parent->left;
			}
			if (bro->left->color == BLACK && bro->right->color == BLACK) {
				bro->color = RED;
				currentNode = currentNode->parent;
			}
			else {
				if (bro->left->color == BLACK) {
					bro->right->color = BLACK;
					bro->color = RED;
					leftRotate(bro);
					bro = currentNode->parent->left;
				}
				bro->color = bro->parent->color;
				bro->parent->color = BLACK;
				bro->left->color = BLACK;
				rightRotate(bro->parent);
				currentNode = root;
			}
		}
	}
	// 将 currentNode 染成黑色(解决根节点或红色节点的问题)
	currentNode->color = BLACK;
}
posted @ 2026-04-06 18:48  Yokino123  阅读(16)  评论(1)    收藏  举报