红黑树 rbTree

红黑树

1 红黑树的定义

1.1 满足红黑树的条件

红黑树需要满足什么条件呢?

  • 根节点和所有外部节点都是黑色;
  • 在根节点到外部节点的路径上,没有连续两个红色节点;
  • 所有根到外部节点的路径上,黑色节点的数目都相同。

请添加图片描述

2 红黑树插入节点

2.1 插入节点的颜色

红黑树中插入的节点是什么颜色?
红黑树中插入的新节点都是红色

  1. 插入的所有节点都是黑色
    • 定义3肯定会被破坏,从根到外部节点的路径上,黑色节点的数目变得不相同;
  2. 插入的所有节点都是红色
    • 如果其父节点是红色,则性质2破坏,调整;
    • 如果其父节点是黑色,则该树还是红黑树,不调整;

2.2 失衡情况和节点调整

红黑树插入节点如何调整?
父节点是黑色,不做调整;父节点是红色,需要调整

2.2.1 失衡情况
  1. LLr
    请添加图片描述

  2. LLb

请添加图片描述

  1. 其它情况
    注意:d在此处都是要删除节点

在这里插入图片描述

2.2.2 节点调整
RXX的操作与以下的LXX的操作相反
(1)变色操作
XXr
LRr的变色操作也是一样的
插入的节点是根节点直接变为黑色

请添加图片描述

//这里的u表视为插入节点
// 获取到插入节点的叔叔节点
rbtree_node* y = u->parent->parent->right;
// LXr ---> 变色
/*
	1.u的父亲节点和叔叔节点变为黑色
	2.u的祖父节点变为红色
	3.将u更新为u的祖父节点 并向上继续检查  
*/
if (y->color == RED)
{
	u->parent->color = BLACK;
	y->color = BLACK;
	u->parent->parent->color = RED;
	u = u->parent->parent;
}
(2)变色+旋转
XXb	

在这里插入图片描述

else if (y->color == BLACK)
{

   // LRb
   /*
   	先转型为LLb型
   		- 将 u 的父亲节点更新为 u
   		- 对 u 进行左旋
   	[接下来的操作就和LLb一样啦~]
   */
   if (u == u->parent->right)
   {
   	u = u->parent;
   	left_rotate(u, T);
   }

   // LLb
   /*
   	1.u的父亲节点变为黑色 u的祖父节点变为红色
   	2.对u的祖父节点为根的子树进行右旋
   */
   u->parent->color = BLACK;
   u->parent->parent->color = RED;
   right_ratate(u->parent->parent, T);
}

3 红黑树删除节点

3.1 大体思路

  1. 节点颜色:
    (1)删除红色节点 —> 无影响 根据下面删除后继节点的方法只改变节点的key值即可;
    (2)删除黑色节点

  2. 节点左右子树:
    (1)删除的节点是叶子节点 —> 直接删
    (2)删除的节点只有一个孩子 —> 有哪个孩子就用哪个孩子来代替
    (3)删除的节点有两个孩子 —> 找(中序遍历)删除节点的后继节点

  3. 检测是否为红黑树
    是红黑树 —> 操作结束
    不是红黑树 —> 修改为正规红黑树

3.2 删除节点

3.2.1 删除红色节点

对下图进行具体说明:

  • 图上: 要删除20这个节点,该节点的右节点只有一个孩子,对红黑树进行中序遍历(即val值从大到小进行排序),用排序结果易得,20的后继节点为30,所以用30继承20的位置;
  • 图下: 要删除20这个节点,但是该节点的右节点有两个孩子,我们不能直接用该节点的右节点去继承,需要通过中序遍历的方法,得到20的后继节点为22,用22代替删除节点;

总之,我们可以通过中序遍历得到删除节点的后继节点,从而用该节点去继承我们要删除的节点
在这里插入图片描述

3.2.2 删除黑色节点

我们假设有 u、x、y 三种节点,其分别代表的含义如下图所示:

在这里插入图片描述

插入节点u为黑色节点,且x、y节点不可能同时为红色,所以u、x、y的组合有下面有下面几种情况:

uxy
黑色红色黑色
黑色黑色红色
黑色黑色黑色
注意:我们所要删除的节点u的继承节点x也是通过中序遍历得到的u的后继节点

让我们来首先讨论存在红色节点的情况,

(1)存在一个红色节点情况

情况1: 删除节点u直接用u的右子树x来继承u的val即可,color不用发生改变;
情况2: 需要进行两步

  • 二叉搜索树删除
  • 将y变为黑色

在这里插入图片描述

(2)全部都为黑色节点情况

情况3: 相较于前两者情况更为复杂,我们还得对情况3的子集情况进行进一步的讨论

由下图我们可以看出,删除节点40违反了红黑树的性质:
在这里插入图片描述

那么我们应该如何去修正呢?我们不妨关注y节点的父亲节点(py)和同辈节点(v),那么我们又可以得到以下四种情况;从图中的文字注释得知,只要我们解决了Rb情况的问题,其它三种情况的问题就能够很好的解决了:

在这里插入图片描述

但是的但是(没错还没完~),我们还要对Rb的情况进行更进进一步的讨论,从节点v的角度去分析,我们大致又可以分为以下三种情况:

Rb0

左黑右黑

在这里插入图片描述

Rb1

左红右黑
左红右红
在这里插入图片描述

Rb2

左黑右红

在这里插入图片描述

如何将 Rr 转换成 Rb

在这里插入图片描述

4 代码示例

不包括删除节点部分

#include <stdio.h>
#include <stdlib.h>

#define RED 1
#define BLACK 2

typedef int KEY_TYPE;

// 定义红黑树的结构体
typedef struct _rbtree_node
{
	unsigned char color;		 // 红是 1 黑是 2
	struct _rbtree_node* right;	 // 指向右孩子的指针
	struct _rbtree_node* left;
	struct _rbtree_node* parent; // 指向父亲节点的指针
	KEY_TYPE key;
	void* value; // 用不到 value
} rbtree_node;

// 再定义一个结构体 保存红黑树的根和外部节点
typedef struct _rbtree
{
	rbtree_node* root;	 // 指向根的指针
	rbtree_node* nil;	 // 外部节点
} rbtree;

// 定义一个函数 生成一个新的节点 给函数节点的key 还有树根
rbtree_node* newNode(rbtree* T, int key)
{
	rbtree_node* node = (rbtree_node*)malloc(sizeof(rbtree_node));
	// 给新节点赋值
	node->color = RED; // 插入红黑树的新节点都是红色
	node->value = NULL;
	node->left = T->nil;
	node->right = T->nil;
	node->parent = T->nil;
	node->key = key;

	return node;
}

// 左旋函数
void left_rotate(rbtree_node* root, rbtree* T)
{
	rbtree_node* newroot = root->right;
	rbtree_node* T2 = newroot->left;

	// root 的右子树变为原来新根的左子树
	// newroot->left = root; ---> 把调成放在寻找父节点之后 (因为要)更改父节点
	root->right = T2;

	// 修改(寻找)父节点 ---> 谁的父节点变了 ---> T2
	if (T2 != T->nil)
		T2->parent = root;

	// 更改新根的父亲节点
	newroot->parent = root->parent;
	if (root->parent == T->nil)
	{
		// 原来的 root 就是整个树的根
		T->root = newroot;
	}
	else if (root->parent->left == root)
	{
		// 原来的树根是左子树 那么新的树根也是左子树
		root->parent->left = newroot;
	}
	else if (root->parent->right == root)
	{
		root->parent->right = newroot;
	}

	// 最后 将旧根变成新根的左孩子
	newroot->left = root;
	root->parent = newroot;
}

// 右旋函数 (和左旋差不多 left 和 right 反过来改就行)
void right_ratate(rbtree_node* root, rbtree* T)
{
	rbtree_node* newroot = root->left;
	rbtree_node* T2 = newroot->right;

	root->left = T2;

	if (T2 != T->nil)
		T2->parent = root;

	newroot->parent = root->parent;
	if (root->parent == T->nil)
	{
		T->root = newroot;
	}
	else if (root->parent->left == root)
	{
		root->parent->left = newroot;
	}
	else if (root->parent->right == root)
	{
		root->parent->right = newroot;
	}

	newroot->right = root;
	root->parent = newroot;
}

// 定义函数 调整插入后的失衡情况
void rbtree_insert_fixup(rbtree* T, rbtree_node* u)
{ // u 代表插入节点
	while (u->parent->color == RED)
	{
		if (u->parent == u->parent->parent->left)
		{
			// LXx
			// 获取到插入节点的叔叔节点
			rbtree_node* y = u->parent->parent->right;
			// LXr ---> 变色
			/*
				1.u的父亲节点和叔叔节点变为黑色
				2.u的祖父节点变为红色
				3.将u更新为u的祖父节点 并向上继续检查  
			*/
			if (y->color == RED)
			{
				u->parent->color = BLACK;
				y->color = BLACK;
				u->parent->parent->color = RED;
				u = u->parent->parent;
			}
			else if (y->color == BLACK)
			{

				// LRb
				/*
					先转型为LLb型
						- 将 u 的父亲节点更新为 u
						- 对 u 进行左旋
					[接下来的操作就和LLb一样啦~]
				*/
				if (u == u->parent->right)
				{
					u = u->parent;
					left_rotate(u, T);
				}

				// LLb
				/*
					1.u的父亲节点变为黑色 u的祖父节点变为红色
					2.对u的祖父节点为根的子树进行右旋
				*/
				u->parent->color = BLACK;
				u->parent->parent->color = RED;
				right_ratate(u->parent->parent, T);
			}
		}
		else
		{
			// RXx 和 LXx 差不多
			rbtree_node* y = u->parent->parent->left;
			// RXr ---> 变色 [没啥差别] 
			if (y->color == RED)
			{
				u->parent->color = BLACK;
				y->color = BLACK;
				u->parent->parent->color = RED;
				u = u->parent->parent;
			}
			else if (y->color == BLACK)
			{

				// RLb
				if (u == u->parent->left)
				{
					u = u->parent;
					right_ratate(u, T);
				}

				// RRb
				u->parent->color = BLACK;
				u->parent->parent->color = RED;
				left_rotate(u->parent->parent, T);
			}
		}
	}
	T->root->color = BLACK;
}

// 定义函数 插入新的节点
void rbtree_insert(rbtree* T, rbtree_node* z)
{
	rbtree_node* y = T->nil;	// 指向我们要插入节点的父节点
	rbtree_node* x = T->root; // 指向我们要插入节点的位置

	// recursion
	while (x != T->nil)
	{
		y = x;
		if (z->key < x->key)
		{
			x = x->left;
		}
		else if (z->key > x->key)
		{
			x = x->right;
		}
		else
		{
			return;
		}
	}

	// 找到 z 的父节点
	z->parent = y;
	// z 插入在 x 的左孩子还是右孩子
	if (y == T->nil)
	{
		// 如果要插入这个节点的父亲节点是 NULL ---> 该节点是整个树的根节点
		T->root = z;
	}
	else if (z->key > y->key)
	{
		y->right = z;
	}
	else if (z->key < y->key)
	{
		y->left = z;
	}

	// 插入后的数是否是一个红黑树呢?
	// 检测 + 调整
	rbtree_insert_fixup(T, z);
}

// 中序遍历
void mid_traversal(rbtree* T, rbtree_node* node)
{
	if (node == T->nil)
		return;
	mid_traversal(T, node->left);
	printf("key:%d, color:%d\n", node->key, node->color);
	mid_traversal(T, node->right);
}

// 先序遍历
void pre_traversal(rbtree* T, rbtree_node* node)
{
	if (node == T->nil)
		return;
	printf("key:%d, color:%d\n", node->key, node->color);
	pre_traversal(T, node->left);
	pre_traversal(T, node->right);
}

int main()
{
	// 10 50 60 62 65 70
	int keyArray[6] = { 10, 50, 60, 62, 65, 70 };
	// 创建一个红黑树
	rbtree* T = (rbtree*)malloc(sizeof(rbtree));

	// 定义外部节点[NULL]
	T->nil = (rbtree_node*)malloc(sizeof(rbtree_node));
	T->nil->color = BLACK;
	T->root = T->nil; // 此时当前这个数还是一个空树

	// 红黑树插入节点
	rbtree_node* node = T->nil;
	for (int i = 0; i < 6; i++)
	{
		node = newNode(T, keyArray[i]);
		rbtree_insert(T, node);
	}
		
	// 检查
	printf("---------中序遍历输出-----------\n");
	mid_traversal(T, T->root);
	printf("---------先序遍历输出-----------\n");
	pre_traversal(T, T->root);
	printf("------------End--------------\n");

	return 0;
}

参考资料

[1] space.bilibili.com/168886653

  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值