一文速学---红黑树

文章目录

  • 一、红黑树简介
  • [二、 红黑树特性](#二、 红黑树特性)
  • 三、红黑树插入
    • [3.1 红黑树为空](#3.1 红黑树为空)
    • [3.2 父节点为黑色](#3.2 父节点为黑色)
    • [3.3 父节点为红色](#3.3 父节点为红色)
      • [3.3.1 父亲和叔叔都是红色](#3.3.1 父亲和叔叔都是红色)
      • [3.3.2 父节点为红色,叔叔节点为黑色](#3.3.2 父节点为红色,叔叔节点为黑色)
        • [3.3.2.1 父节点在左节点,插入节点在父亲左节点](#3.3.2.1 父节点在左节点,插入节点在父亲左节点)
        • [3.3.2.2 父节点在左节点,插入节点在父亲右节点](#3.3.2.2 父节点在左节点,插入节点在父亲右节点)
        • [3.3.2.3 父节点在右节点,插入节点在父亲右节点](#3.3.2.3 父节点在右节点,插入节点在父亲右节点)
        • [3.3.2.4 父节点在右节点,插入节点在父亲左节点](#3.3.2.4 父节点在右节点,插入节点在父亲左节点)
  • 四、红黑树删除
    • [4.1 删除既有左子树又有右子树节点](#4.1 删除既有左子树又有右子树节点)
    • [4.2 删除有左子树或者右子树节点](#4.2 删除有左子树或者右子树节点)
      • [4.2.1 不存在情况](#4.2.1 不存在情况)
      • [4.2.2 存在情况](#4.2.2 存在情况)
      • [4.2.3 删除节点](#4.2.3 删除节点)
    • [4.3 删除叶子节点](#4.3 删除叶子节点)
      • [4.3.1 叶子节点是红色](#4.3.1 叶子节点是红色)
      • [4.3.2 叶子节点是黑色](#4.3.2 叶子节点是黑色)
        • [4.3.2.1 叶子节点是左节点,兄弟节点红色](#4.3.2.1 叶子节点是左节点,兄弟节点红色)
        • [4.3.2.2 叶子节点是左节点,兄弟节点黑色](#4.3.2.2 叶子节点是左节点,兄弟节点黑色)
          • [4.3.2.2.1 兄弟节点右孩子为红色,左孩子任意颜色](#4.3.2.2.1 兄弟节点右孩子为红色,左孩子任意颜色)
          • [4.3.2.2.2 兄弟节点左孩子为红色,右孩子为黑色](#4.3.2.2.2 兄弟节点左孩子为红色,右孩子为黑色)
          • [4.3.2.2.3 兄弟节点左右孩子为黑色](#4.3.2.2.3 兄弟节点左右孩子为黑色)
        • [4.3.2.3 叶子节点是右节点,兄弟节点红色](#4.3.2.3 叶子节点是右节点,兄弟节点红色)
        • [4.3.2.4 叶子节点是右节点,兄弟节点黑色](#4.3.2.4 叶子节点是右节点,兄弟节点黑色)
          • [4.3.2.4.1 兄弟节点左孩子为红色,右孩子任意颜色](#4.3.2.4.1 兄弟节点左孩子为红色,右孩子任意颜色)
          • [4.3.2.4.2 兄弟节点右孩子为红色,左孩子黑色](#4.3.2.4.2 兄弟节点右孩子为红色,左孩子黑色)
          • [4.3.2.4.3 兄弟节点左右孩子为黑色](#4.3.2.4.3 兄弟节点左右孩子为黑色)
  • 五、红黑树查询
  • 六、红黑树中序遍历

一、红黑树简介

以前只是在考研学408的时候接触到红黑树,但是当时并没有做深入的了解。最近在做一个KV存储的项目,Key-Value的存储需要一个比数组更佳高效进行插入和删除的数据结构。红黑树,hash都是不错的用来存储的数据结构。

红黑树也是一种自平衡二叉查找树,它与AVL树类似,都在添加和删除的时候通过旋转操作保持二叉树的平衡,以求更高效的查询性能。

与AVL树相比,红黑树牺牲了部分平衡性,以换取插入/删除操作时较少的旋转操作,整体来说性能要优于AVL树。

二、 红黑树特性

红黑树是实际应用中最常用的平衡二叉查找树,它不严格的具有平衡属性,但平均的使用性能非常良好。

在红黑树中,节点被标记为红色和黑色两种颜色。

红黑树原则有以下几点:

1,根节点和叶节点一定是黑色(根叶黑 )

2,从叶子到根的两个连续节点不能都是红色节点(不红红 )

3,父节点的值大于左节点的值,小于右节点的值(左根右 )

4,从任一节点到其他每个叶子的所有路径包含相同数目的黑色节点(黑路同)

三、红黑树插入

红黑树节点和树的结构体定义:

c 复制代码
typedef struct _rbtree_node {
	unsigned char color;
	struct _rbtree_node *right;
	struct _rbtree_node *left;
	struct _rbtree_node *parent;
	KEY_TYPE key;
	void *value;
} rbtree_node;

typedef struct _rbtree {
	rbtree_node *root;
	rbtree_node *nil;
} rbtree;

因为父节点为黑色的概率较大,插入新节点为红色,可以避免颜色冲突,所以默认插入节点的颜色为红色

3.1 红黑树为空

直接插入节点,根据根叶黑的特性,设置为黑色

3.2 父节点为黑色

由于插入的是红色,不影响红黑树平衡

3.3 父节点为红色

因为父节点是红色,所以父节点不可能是根节点

父节点为红色时,会出现两种情况:1,叔叔为红色;2,叔叔为黑色;

3.3.1 父亲和叔叔都是红色

处理方式:

1,将M和N变黑,P变红;

2,将P设置为当前节点;

如果P的父节点是黑色则无需处理;如果P的父节点是红色,违反了不红红特性,继续调整;

3.3.2 父节点为红色,叔叔节点为黑色

3.3.2.1 父节点在左节点,插入节点在父亲左节点

这是一种插入后的LL型失衡

处理方式:

1,对P和M变色;

2,对P右旋;

3.3.2.2 父节点在左节点,插入节点在父亲右节点

这是一种插入后的LR型失衡

处理方式:

1,对M进行左旋;

2,将M设置为当前节点;

3,转换为 父节点在左节点,插入节点在父亲左节点 情况

3.3.2.3 父节点在右节点,插入节点在父亲右节点

这是一种插入后的RR型失衡

处理方式:

1,将M和P变色;

2,对P左旋;

3.3.2.4 父节点在右节点,插入节点在父亲左节点

这是一种插入后的RL型失衡

处理方式:

1,对M点右旋;

2,将M设置为当前节点;

3,转换为 父节点在右节点,插入节点在父亲左节点 情况

下面的代码是关于红黑树插入的实现:

c 复制代码
//x为需要左旋的节点
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
	//支点
	rbtree_node *y = x->right;  // x  --> y  ,  y --> x,   right --> left,  left --> right

	//支点左节点赋给x右节点
	x->right = y->left; 
	if (y->left != T->nil) { //更改支点左节点的父节点
		y->left->parent = x;
	}

	y->parent = x->parent; //1 3
	if (x->parent == T->nil) { //x是root节点情况
		T->root = y;
	} else if (x == x->parent->left) {//x父节点的左孩子
		x->parent->left = y;
	} else {
		x->parent->right = y;
	}

	y->left = x; //1 5
	x->parent = y; //1 6
}

//y为需要右旋的节点
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
	//支点pivot
	rbtree_node *x = y->left;
	
	y->left = x->right;
	if (x->right != T->nil) {
		x->right->parent = y;
	}

	x->parent = y->parent;
	if (y->parent == T->nil) {//y是root情况
		T->root = x;
	} else if (y == y->parent->right) {//y是右孩子
		y->parent->right = x;
	} else {//y是左孩子
		y->parent->left = x;
	}

	x->right = y;
	y->parent = x;
}

void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
	//父亲节点如果是黑色,插入红色节点可以不变化
	while (z->parent->color == RED) { //z ---> RED
		//判断是爷爷节点的左边的L型还是右边的R型
		if (z->parent == z->parent->parent->left) {//L型
			//指向叔叔节点
			rbtree_node* y = z->parent->parent->right;
			//叔叔节点为红
			if (y->color == RED) {
				//变化叔叔,父亲,爷爷节点的颜色
				z->parent->color = BLACK;
				y->color = BLACK;
				z->parent->parent->color = RED;
				//将爷爷节点设置为当前节点
				z = z->parent->parent; //z --> RED
			} else {//叔叔节点为黑
				//将LR型转为LL型处理
				if (z == z->parent->right) {
					z = z->parent;	//这行代码用于当是LR型时,将z->parent设置为当前节点
					//对插入节点的父节点左旋
					rbtree_left_rotate(T, z);
				}
				//LL型
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				//将当前节点的爷爷节点右旋
				rbtree_right_rotate(T, z->parent->parent);
			}
		}
		else {//R型
			//指向叔叔节点
			rbtree_node *y = z->parent->parent->left;
			if (y->color == RED) {//叔叔节点为红色
				z->parent->color = BLACK;
				y->color = BLACK;
				z->parent->parent->color = RED;
				//将爷爷节点设置为当前节点
				z = z->parent->parent; //z --> RED
			} else {//叔叔节点为黑色
				if (z == z->parent->left) {//RL型
					//设置 z->parent为当前节点
					z = z->parent;
					//z->parent右转
					rbtree_right_rotate(T, z);
				}
				//RR型
				z->parent->color = BLACK;
				z->parent->parent->color = RED;
				//当前节点的爷爷节点左旋
				rbtree_left_rotate(T, z->parent->parent);
			}
		}
		
	}

	T->root->color = BLACK;
}


void rbtree_insert(rbtree *T, rbtree_node *z) {
	//指向叶节点
	rbtree_node* y = T->nil;
	//指向根节点
	rbtree_node* x = T->root;
	//y指向将要插入节点的父节点
	while (x != T->nil) {
		y = x;
		if (z->key < x->key) {
			x = x->left;
		} else if (z->key > x->key) {
			x = x->right;
		} else { //Exist
			return ;
		}
	}

	z->parent = y;
	if (y == T->nil) { //是否为空树
		T->root = z;
	} else if (z->key < y->key) { //插入左子树
		y->left = z;
	} else { //插入右子树
		y->right = z;
	}

	z->left = T->nil;
	z->right = T->nil;
	//将插入节点设置为红色
	z->color = RED;

	rbtree_insert_fixup(T, z);
}

四、红黑树删除

根据红黑树的性质,我们要删除的节点类型大致分为三种:

1,叶子节点

2,有左子树或者右子树节点

3,既有左子树又有右子树节点

4.1 删除既有左子树又有右子树节点

对于一棵普通二叉树来说,要删除既有左子树又有右子树的节点,我们首先要找到该节点的直接后继节点,然后用后继节点替换该节点,最后按1或2中的方法删除后继节点即可。所以情况3可以转换为情况1或2

对于红黑树来说,我们实际上删除的节点情况只有1和2。

4.2 删除有左子树或者右子树节点

4.2.1 不存在情况

情况二中有很多情况其实是不存在的,这些情况都违背了红黑树的性质(P代表需要删除的节点 )

上面四种情况违背了黑路同的性质(P代表需要删除的节点 )

上面两种情况违背了不红红的性质(P代表需要删除的节点)

4.2.2 存在情况

结合上面的分析,我们能发现对于只有左子树或者右子树的类型,其实只有下面的组合类型(P代表需要删除的节点 )

4.2.3 删除节点

这两种情况的处理方法都是一样的,使用P的孩子M替换P,并且将M的颜色改为黑色即可。

4.3 删除叶子节点

4.3.1 叶子节点是红色

上面这两种情况都是一样的,直接删除P节点

4.3.2 叶子节点是黑色

4.3.2.1 叶子节点是左节点,兄弟节点红色

处理过程:

1,将父节点P和兄弟节点M交换颜色;(D是要删除节点,是当前节点 )

2,对P左旋;

这个结果演变成后面讨论的兄弟节点是黑色情况(D是要删除节点)

4.3.2.2 叶子节点是左节点,兄弟节点黑色
4.3.2.2.1 兄弟节点右孩子为红色,左孩子任意颜色

白色表示红色或者黑色都可以

处理过程:(D是要删除节点,是当前节点 )

1,P和M颜色对调

2,P进行左旋

3,删除D

4,MR设置为黑色

4.3.2.2.2 兄弟节点左孩子为红色,右孩子为黑色

白色表示红色或者黑色都可以

处理过程:(D是要删除节点,是当前节点 )

1,ML和M颜色对调

2,M进行左旋

3,情况转换为 兄弟节点右孩子为红色,左孩子任意颜色

4.3.2.2.3 兄弟节点左右孩子为黑色
父亲节点红色

处理过程:(D是要删除节点,是当前节点 )

1,P和M颜色对调

2,删除D

父亲节点黑色

处理过程:(D是要删除节点,是当前节点 )

1,M颜色设置为红色

2,删除D

4.3.2.3 叶子节点是右节点,兄弟节点红色

处理过程:(D是要删除节点,是当前节点 )

1,P和M颜色对调

2,P进行左旋

这个结果演变成后面讨论的兄弟节点是黑色情况(D是要删除节点)

4.3.2.4 叶子节点是右节点,兄弟节点黑色
4.3.2.4.1 兄弟节点左孩子为红色,右孩子任意颜色

处理过程:(D是要删除节点,是当前节点 )

1,P和M颜色对调

2,P进行左旋

3,删除D

4,ML设置为黑色

4.3.2.4.2 兄弟节点右孩子为红色,左孩子黑色

白色表示红色或者黑色都可以

处理过程:(D是要删除节点,是当前节点 )

1,MR和M颜色对调

2,M进行左旋

3,情况转换为 兄弟节点左孩子为红色,右孩子任意颜色

4.3.2.4.3 兄弟节点左右孩子为黑色
父亲节点红色

处理过程:(D是要删除节点,是当前节点 )

1,P和M颜色对调

2,删除D

父亲节点黑色

处理过程:(D是要删除节点,是当前节点 )

1,M颜色设置为红色

2,删除D

下面是实现红黑树删除的代码:

c 复制代码
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
	//对于有左子树或者右子树情况,因为只有黑红模式,所以传进来的x只能是红色
	//对于有左右子树情况可以转换为叶子结点或者只有左子树或者又子树情况
	//下面的循环主要用于处理叶子结点是黑色的情况且叶子节点的空节点不为根节点(代表树为空)
	while ((x != T->root) && (x->color == BLACK)) {
		//删除节点是左孩子
		if (x == x->parent->left) {
			//删除节点的兄弟节点
			rbtree_node *w= x->parent->right;
			if (w->color == RED) {//如果兄弟节点为红色
				//父节点和兄弟节点颜色互换
				w->color = BLACK;
				x->parent->color = RED;
				//父节点左旋
				rbtree_left_rotate(T, x->parent);
				//更新被删除节点的兄弟节点
				w = x->parent->right;
			}
			//兄弟节点为黑色,兄弟节点左右孩子都是黑色
			if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
				//兄弟节点设置为红
				w->color = RED;
				//重新设置起始点点
				x = x->parent;
			} else {
				//兄弟节点为黑色,右孩子是黑色,左孩子红色---》右孩子变为红色
				if (w->right->color == BLACK) {
					//w和w的
					w->left->color = BLACK;
					w->color = RED;
					//兄弟节点右旋
					rbtree_right_rotate(T, w);
					//更新删除节点的兄弟节点
					w = x->parent->right;
				}
				//兄弟节点为黑色,右孩子为红色情况
				//兄弟节点和父节点颜色互换
				w->color = x->parent->color;
				x->parent->color = BLACK;
				//变换兄弟节点右孩子颜色
				w->right->color = BLACK;
				//对父节点做左旋
				rbtree_left_rotate(T, x->parent);
				//结束
				x = T->root;
			}
		//删除节点是右孩子
		} else {
			//删除节点的兄弟节点
			rbtree_node *w = x->parent->left;
			if (w->color == RED) {//如果兄弟节点为红色
				//父节点和兄弟节点颜色互换
				w->color = BLACK;
				x->parent->color = RED;
				//父节点右旋
				rbtree_right_rotate(T, x->parent);
				//更新被删除节点的兄弟节点
				w = x->parent->left;
			}
			//兄弟节点为黑色,兄弟节点左右孩子都是黑色
			if ((w->left->color == BLACK) && (w->right->color == BLACK)) {
				//兄弟节点设为红色
				w->color = RED;
				//重新设置起始点点
				x = x->parent;
			} else {
				//兄弟节点为黑色,左孩子是黑色,右孩子红色---》左孩子变为红色
				if (w->left->color == BLACK) {
					w->right->color = BLACK;
					w->color = RED;
					rbtree_left_rotate(T, w);
					w = x->parent->left;
				}
				//兄弟节点为黑色,左孩子为红色情况
				//兄弟节点和父节点颜色互换
				w->color = x->parent->color;
				x->parent->color = BLACK;
				w->left->color = BLACK;
				rbtree_right_rotate(T, x->parent);
				//结束
				x = T->root;
			}

		}
	}
	//设置为黑色
	x->color = BLACK;
}

rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
	//z指向想删除节点,y指向当前节点,x指向当前节点的孩子
	rbtree_node *y = T->nil;
	rbtree_node *x = T->nil;

	//z节点至多有一个孩子节点
	if ((z->left == T->nil) || (z->right == T->nil)) {
		y = z;//当前节点设置为要删除节点
	} else {//z节点有两个孩子节点
		y = rbtree_successor(T, z);//当前节点设置为后继节点
	}

	//双孩子改变后的当前点左右两个节点都是空节点
	if (y->left != T->nil) {//只有左孩子情况
		x = y->left;
	} else if (y->right != T->nil) {//只有右孩子情况
		x = y->right;
	}

	//直接使用平衡二叉树情况删除节点
	x->parent = y->parent;
	if (y->parent == T->nil) {
		T->root = x;
	} else if (y == y->parent->left) {
		y->parent->left = x;
	} else {
		y->parent->right = x;
	}
	
	if (y != z) {
		z->key = y->key;
		z->value = y->value;
	}
	//删除红色节点没有影响
	if (y->color == BLACK) {
		rbtree_delete_fixup(T, x);
	}

	return y;
}

五、红黑树查询

因为红黑树的性质中有左跟右,所以每次只需要和父节点比较大小即可,下面是查询实现代码:

c 复制代码
rbtree_node *rbtree_search(rbtree *T, KEY_TYPE key) {

	rbtree_node *node = T->root;
	while (node != T->nil) {
		if (key < node->key) {
			node = node->left;
		} else if (key > node->key) {
			node = node->right;
		} else {
			return node;
		}	
	}
	return T->nil;
}

六、红黑树中序遍历

根据中序遍历的规则,实现中序遍历红黑树的代码

c 复制代码
void rbtree_traversal(rbtree *T, rbtree_node *node) {
	if (node != T->nil) {
		rbtree_traversal(T, node->left);
		printf("key:%d, color:%d\n", node->key, node->color);
		rbtree_traversal(T, node->right);
	}
}
相关推荐
ahadee20 分钟前
蓝桥杯每日真题 - 第17天
c语言·vscode·算法·蓝桥杯
睡觉然后上课20 分钟前
C++笔试面试题
c语言·c++·面试
知星小度S2 小时前
数据结构——排序(续集)
c语言·数据结构·算法
搬砖的小码农_Sky3 小时前
C语言:链表
c语言·开发语言·链表
Mcworld8574 小时前
C语言:内存动态分配
c语言·开发语言·算法
simple_ssn4 小时前
【C语言刷力扣】58.最后一个单词的长度
c语言·算法·leetcode
是糖不是唐4 小时前
代码随想录算法训练营第五十天|Day50 图论
c语言·数据结构·算法·图论
陈鋆6 小时前
MySQL深入:B+树的演化、索引和索引结构
数据库·b树·mysql
带多刺的玫瑰6 小时前
Leecode刷题C语言之最少翻转次数使二进制矩阵回文①
c语言·开发语言