数据结构:平衡二叉树之红黑树插入删除

他们以为击败我就是终结,而我必归来将之改写。

一、红黑树:

注:文章中用黑色表示黑色结点,红色表示红色结点,绿色表示红色或黑色结点

概念:

红黑树是一种自平衡二叉查找树,它通过特定的性质和操作来维持树的平衡,确保在最坏情况下的基本动态集合操作(如查找、插入、删除)的时间复杂度保持在 O(log n)。红黑树的引入旨在提供一种简洁而高效的平衡二叉树实现方式,相比于其他自平衡二叉树(如AVL树),红黑树在保持性能的同时,其平衡调整过程更为直观和简洁。

性质:

节点颜色:每个节点要么是红色的,要么是黑色的。

根节点颜色:根节点是黑色的。这个性质确保了树的高度不会因根节点为红色而减少。

叶子节点(NIL节点):所有的叶子节点(NIL节点,即空节点)都是黑色的。

根节点初始化

#define NIL (&__NIL)
typedef struct Node {
	int color, key;
	struct Node* lchild, * rchild;
}Node;
//全局变量要在main中使用,需要在.cpp中用extern声明,并且还要定义在.cpp文件中。
extern Node __NIL;
//初始化NIL结点(虚拟空结点)
void initNIL() {
	NIL->key = -1;
	NIL->color = BLACK;
	NIL->lchild = NIL->rchild = NIL;
	return;
}

红色节点的子节点:如果一个节点是红色的,则它的两个子节点都必须是黑色的。这个性质确保了在树中不会存在过长的红色路径,从而限制了树的高度。

黑高:从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

以上性质保证了在最差情况下,一条支路的长度为另外一条支路的两倍,从而达到平衡效果使得二叉树不会退化为链表。

二、平衡调整操作

左旋:

绕着根结点进行左旋,原来的左结点成为新的根结点,原根结点的右子树变为原根结点右子树的左子树。
在这里插入图片描述

Node* left_rotate(Node* root) {
	Node* newRoot = root->rchild;
	root->rchild = newRoot->lchild;
	newRoot->lchild = root;
	return newRoot;
}

右旋:

绕着根结点进行右旋,原来的右结点成为新的根结点,原根结点的左子树变为原根结点左子树的右子树。
在这里插入图片描述

Node* right_rotate(Node* root) {
	Node* newRoot = root->lchild;
	root->lchild = newRoot->rchild;
	newRoot->rchild = root;
	return newRoot;
}

三、红黑树的插入:

红黑树的插入操作:

如果目标值大于当然结点根值,递归到右侧插入,反之递归到左侧。

Node* __insert(Node* root, int key) {
	if (root == NIL) return getNewNode(key);
	if (root->key == key) return root;
	if (root->key < key) root->rchild = __insert(root->rchild, key);
	else root->lchild = __insert(root->lchild, key);
	return insert_maintain(root);
}

Node* insert(Node* root, int key) {
	root = __insert(root, key);
	root->color = BLACK;
	return root;
}

红黑树插入调整:

在插入时,我们插入的结点为红色,可能会出现两个红色结点挨在一起的情况,进行插入调整。

插入调整站在祖父结点向下看

情况一:

根节点左右子树为红色
在这里插入图片描述

情况二:

根节点左右子树中含有一个红色结点,该红色结点子结点含有红色结点

右右

在这里插入图片描述

右左

在这里插入图片描述

左右

参考右左

左左

参考右右

Node* insert_maintain(Node* root) {
	int flag = 0;
	if (root->lchild->color == RED && has_red_node(root->lchild)) flag = 1;
	if (root->rchild->color == RED && has_red_node(root->rchild)) flag = 2;
	if (flag == 0) return root;
	if (root->lchild->color == RED && root->rchild->color == RED) goto red_up_maintain;
	if (flag == 1) {
		if (root->lchild->rchild->color == RED) {
			root->lchild = left_rotate(root->lchild);
		}
		root = right_rotate(root);
	}
	else {
		if (root->rchild->lchild->color == RED) {
			root->rchild = right_rotate(root->rchild);
		}
		root = left_rotate(root);
	}
red_up_maintain:
	root->color = RED;
	root->lchild->color = root->rchild->color = BLACK;
	return root;
}

四、红黑树的删除:

红黑树的删除操作:

如果目标值大于当然结点根值,递归到右侧删除,反之递归到左侧。删除度为0/1/2的结点,我们只需要将度为2的结点转化为删除度为1或者2的结点,下面演示度为0/1的情况:
删除红色结点:
分析可知:度为一的红色结点不存在,度为0的结点如图
在这里插入图片描述

删除黑色结点:
在这里插入图片描述

在这里插入图片描述

代码演示:

Node* __erase(Node* root, int key) {
	if (root == NIL) return root;
	if (root->key < key) root->rchild = __erase(root->rchild, key);
	else if (root->key > key) root->lchild = __erase(root->lchild, key);
	else {
		if (root->lchild == NIL || root->rchild == NIL) {
			Node* temp = root->lchild != NIL ? root->lchild : root->rchild;
			temp->color += root->color;//这里包含了上图三种情况,十分精髓。
			free(root);
			return temp;
		}
		Node* temp = predecessor(root);
		root->key = temp->key;
		root->lchild = __erase(root->lchild, temp->key);
	}
	return erase_maintain(root);
}

Node* erase(Node* root, int key) {
	root = __erase(root, key);
	root->color = BLACK;
	return root;
}

红黑树的删除调整:

删除操作中我们已经删除了目标结点,我们之前定义红黑树只能有红黑节点,接下来的删除调整就是除去删除操作中所产生的那个双重黑结点

站在父节点角度删除调整

情况一:

双重黑的兄弟结点为红色,先进行左旋,原根结点颜色改为红色,新根结点改为黑色。这样就把情况一改成了下面的其他情况,然后递归到15结点进行删除调整。(红色结点在右相反)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXgCvID1-1720923327227)(https://i-blog.csdnimg.cn/direct/7dae4676878e472f95e1cde2045579d7.png)]

情况二:

双重黑兄弟结点为黑色,并且兄弟结点的子结点都为黑色。
根结点颜色加一,根节点子结点颜色减一。

情况三:

双重黑兄弟结点为黑色,并且兄弟结点的子结点存在红色结点,红色结点在同侧。
如果红色结点在同侧和异侧同时存在,优先处理同侧。

右右:
先对根节点进行左旋,新根结点的颜色改为原根结点(原根结点颜色未知,小编用绿色表示)的颜色,根节点两侧的子孩子都改为黑色。
在这里插入图片描述

右右:
虽然右子树的左孩子为红色,优先按照右右进行处理。
在这里插入图片描述

情况四:

双重黑兄弟结点为黑色,并且兄弟结点的子结点存在红色结点,红色结点在异侧。

右左:
先对原根节点的右子树进行右旋,再对原根节点进行左旋,新根结点的颜色改为原根结点(原根结点颜色未知,小编用绿色表示)的颜色,新根节点两侧的子孩子都改为黑色。

在这里插入图片描述

代码演示:

Node* erase_maintain(Node* root) {
	if (root->lchild->color != DOUBLACK && root->rchild->color != DOUBLACK) return root;
	if (has_red_node(root)) {
		root->color = RED;
		if (root->lchild->color == RED) {
			root = right_rotate(root);
			root->rchild = erase_maintain(root->rchild);
		}
		else {
			root = left_rotate(root);
			root->lchild = erase_maintain(root->lchild);
		}
		root->color = BLACK;
		return root;
	}
	if ((root->lchild->color == DOUBLACK && !has_red_node(root->rchild)) ||
		(root->rchild->color == DOUBLACK && !has_red_node(root->lchild))) {
		root->color += 1;
		root->lchild->color -= 1;
		root->rchild->color -= 1;
		return root;
	}
	if (root->lchild->color == DOUBLACK) {
		root->lchild->color = BLACK;
		if (root->rchild->rchild->color != RED) {
			root->rchild = right_rotate(root->rchild);
		}
		root->rchild->color = root->color;
		root = left_rotate(root);
	}
	else {
		root->rchild->color = BLACK;
		if (root->lchild->lchild->color != RED) {
			root->lchild = left_rotate(root->lchild);
		}
		root->lchild->color = root->color;
		root = right_rotate(root);
	}
	root->lchild->color = root->rchild->color = BLACK;
	return root;
}

五、小结:

通过这次学习,小编发现新大陆,加用了目录,也熟悉了画图操作。感谢大家的支持,小编努力更新更优秀的创作,希望与各位一起提升自己!
大家动动小手支持一下呗!

在这里插入图片描述

  • 34
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值