一文速学---红黑树

embedded/2024/11/19 16:19:11/

文章目录

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

一、红黑树简介

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

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

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

二、 红黑树特性

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

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

红黑树原则有以下几点:
1,根节点和叶节点一定是黑色(根叶黑)
2,从叶子到根的两个连续节点不能都是红色节点(不红红)
3,父节点的值大于左节点的值,小于右节点的值(左根右)
4,从任一节点到其他每个叶子的所有路径包含相同数目的黑色节点(黑路同)

三、红黑树插入

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

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,转换为 父节点在右节点,插入节点在父亲左节点 情况
在这里插入图片描述
下面的代码是关于红黑树插入的实现:


//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 3if (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 5x->parent = y; //1 6
}//y为需要右旋的节点
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {//支点pivotrbtree_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 { //Existreturn ;}}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
在这里插入图片描述

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

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;
}

五、红黑树查询

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

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;
}

六、红黑树中序遍历

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

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);}
}

http://www.ppmy.cn/embedded/138807.html

相关文章

树莓派(Raspberry Pi)picotool

树莓派&#xff08;Raspberry Pi&#xff09;picotool 安装直接安装从源码安装工具介绍显示信息保存程序二进制信息基本信息引脚完整信息 链接 安装 直接安装 在archlinux上&#xff0c;使用yay直接安装 yay -S picotool从源码安装 安装libusb sudo pacman -S libusb下载 …

SQL,力扣题目1369,获取最近第二次的活动

一、力扣链接 LeetCode_1369 二、题目描述 表: UserActivity ------------------------ | Column Name | Type | ------------------------ | username | varchar | | activity | varchar | | startDate | Date | | endDate | Date | -----…

云原生周刊:Istio 1.24.0 正式发布

云原生周刊&#xff1a;Istio 1.24.0 正式发布 开源项目推荐 Kopf Kopf 是一个简洁高效的 Python 框架&#xff0c;只需几行代码即可编写 Kubernetes Operator。Kubernetes&#xff08;K8s&#xff09;作为强大的容器编排系统&#xff0c;虽自带命令行工具&#xff08;kubec…

第十六届蓝桥杯模拟赛(第一期)-c++/c

前面填空参考https://blog.csdn.net/2301_76891851/article/details/143448411 后面大题code&#xff1a; 停车场停车 #include <bits/stdc.h> #define int long long using namespace std; int tmp; signed main() {int n;cin>>n;if(n<15)cout<<"…

使用 AWR 进行 Exadata 性能诊断

本文内容来自Oracle 2024年3月发布的白皮书&#xff1a;Exadata Performance and AWR: Exadata Performance Diagnostics with AWR 简介 本技术简介概述了如何将 Oracle AWR 功能与 Exadata 结合使用&#xff0c;从 Exadata 的角度&#xff08;standpoint&#xff09;监控和分…

飞凌嵌入式RK3576核心板已适配Android 14系统

在今年3月举办的RKDC2024大会上&#xff0c;飞凌嵌入式FET3576-C核心板作为瑞芯微RK3576处理器的行业首秀方案重磅亮相&#xff0c;并于今年6月率先量产发货&#xff0c;为客户持续稳定地供应&#xff0c;得到了众多合作伙伴的认可。 FET3576-C核心板此前已提供了Linux 6.1.57…

量子前沿英雄谱|光量子计算的前沿探险家:Jeremy O‘Brien

大航海时代&#xff0c;书写了一部人类探索与发现的壮丽史诗&#xff0c;而作为宏大叙事背后若干个体之一&#xff0c;那些大大小小航船上载着的&#xff0c;是一群不断航向未知海域的坚定探险家。 日光底下无新事。从著名物理学家费曼提出量子计算机的概念起 ... 大航海时代&a…

Go语言的创始人, 核心特性和学习资源

Go语言的创始人 Go语言的创始人有三位&#xff0c;分别是&#xff1a; Robert Griesemer&#xff1a;他参与开发了Java HotSpot虚拟机。Rob Pike&#xff1a;他是Go语言项目的总负责人&#xff0c;曾是贝尔实验室Unix团队的成员&#xff0c;参与过Plan 9、Inferno操作系统和L…