本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
此文章记录个人学习红黑树时的相关笔记,需要您具备:
- 二叉树相关知识
- 基本的C语言编程能力
若文章内容对您有所帮助,那将是我的荣幸!
红黑树的基本介绍
- 红黑树是一种自平衡二叉查找树,与AVL树(平衡二叉树)相比,红黑树在工程上实现起来更为简单,而且其平衡的代价更低【毕竟不是严格看所有结点的高度差,而是看黑色结点的高度差】。国内有论文指出红黑树平均统计性能强于AVL
- 红黑树平衡是以黑色结点的高度作为衡量标准
红黑树使用在什么场景
- map
- nginx
- 定时器
- 进程调度(cfs)
- 内存管理(内存块索引: 存储地址 + 内存长度)
红黑树的相关性质
特点
- 结点是红色或者黑色
- 根节点是黑色
- 所有的叶子结点都是黑色(叶子是NIL结点)
- 如果一个结点是红的,那么它的两个儿子都是黑的【不可出现连续两个红色结点,即父节点和当前节点不能都是红色的】
- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点*【这句话意思是,红黑树当中,黑高是要完全相同的!】
若不满足这5条特点的,不能称之为红黑树。其中第五点很容易忽略,
e.g
这种是不行的,直接看最左和最右两条线可知,这两条路径上的黑高不一样(黑色结点数量不相等)
红黑树相关操作
旋转
左旋
理论上的流程:
- X结点右指针指向b结点【x与y已经断开】
- y结点左指针指向x结点【y与B断开】
工程上的流程【树的结点多了个parent,而且还要考虑左右指针是否指向NIL(空节点)】
备注:铅笔为父指针,黑色签字笔为左右子树指针
-
x的右子树指向y的左子树
a. 如果y的左子树(b)不为空,b的父指针指向x | 如果y的左子树b为空,不处理
-
y的父指针指向x的父指针
a. 若x的parent是根节点【即x是根节点】,那么y就要变成根节点 b. 若x是x父节点的左子树,那么x父节点的左指针指向y c. 若x是x父节点的右子树,那么x父节点的右指针指向y
-
y的左指针指向x
-
x的父节点指向y
右旋
理论上的流程:
- y结点左指针指向b结点【x与y已经断开】
- x结点右指针指向y结点【y与B断开】
工程上的流程【树的结点多了个parent,而且还要考虑左右指针是否指向NIL(空节点)】
备注:铅笔为父指针,黑色签字笔为左右子树指针 本质上,这里的流程和前面左旋的流程一模一样,只是方向完全相反。
-
y指向x的右子树 a. 如果x的右子树(b)不为空,b的父指针指向y | 如果x的右子树b为空,不处理
-
x的父指针指向y的父指针【即让x指向parent结点】 a. 若y的parent是根节点【即y是根节点】,那么x就要变成根节点 b. 若y是y父节点的左子树,那么y父节点的左指针指向x c. 若y是y父节点的右子树,那么y父节点的右指针指向x
-
x的右指针指向y
-
y的父节点指向x
插入结点
红黑树添加结点后,视添加的结点情况,平衡会被打破。
- 根据排序二叉树的方式,将节点按照大小顺序找到具体位置,插入【这种插入肯定实在叶子节点处发生】。所以默认插入结点为红色。
- 若插入的节点的父节点是红色结点,那么就需要调整。若是黑色结点,就无需调整。
当插入结点【当前节点】和其父节点都是红色结点时,需要进行调整:
父节点是祖父节点的左孩子
父节点和叔叔结点【叔父结点】均为红色:
插入89。
解决办法就是变色
- 父节点变为黑色
- 叔叔结点变为黑色
- 祖父节点变为红色
但是祖父节点变色之后,如果祖父节点的父节点也是红色,那么就有问题了!
故,需要向上调整。即将当前节点设置为祖父节点,接着对“当前节点”进行操作。
父节点和叔叔结点【叔父结点】为一红一黑
【即叔结点为黑色的】
- 当前节点是父节点的右孩子【需先将右孩子变为左孩子,再进行操作】
首先以父节点【344】作为支点,进行左旋:
此时将当前节点从【350】变为【344】
则变为执行【当前节点是父节点的左孩子】的流程
- 当前节点是父节点的左孩子
- 将父节点【350】变为黑色
- 将祖父节点【359】变为红色
- 以祖父节点【359】为支点进行右旋
父节点是祖父节点的右孩子
父节点和叔叔结点【叔父结点】均为红色:
插入【799】
解决办法就是变色
- 父节点变为黑色
- 叔叔结点变为黑色
- 祖父节点变为红色
但是祖父节点变色之后,如果祖父节点的父节点也是红色,那么就有问题了!
故,需要向上调整。即将当前节点设置为祖父节点,接着对“当前节点”进行操作。
父节点和叔叔结点【叔父结点】为一红一黑
【即叔结点为黑色的】
- 当前节点是父节点的左孩子【需先将左孩子变为右孩子,再进行操作】
首先以父节点【379】作为支点,进行右旋:
则变为执行【当前节点是父节点的右孩子】的流程
- 当前节点是父节点的右孩子
- 将父节点【365】变为黑色
- 将祖父节点【359】变为红色
- 以祖父节点【359】为支点进行左旋
删除节点
未完待续……
手写红黑树代码
#include <iostream>
using namespace std;
typedef int KEY_TYPE;
struct rbtree_node {
unsigned char color;
struct rbtree_node *parent;
struct rbtree_node *right;
struct rbtree_node *left;
KEY_TYPE key;
};
struct rbtree {
rbtree_node *root;
rbtree_node *nil; // 哨兵
};
// 旋转
// 旋转不涉及到颜色的变化!
void rbtree_left_rotate(rbtree *T, rbtree_node *x) {
// nullptr --> T->nil
if (x == T->nil)
return;
rbtree_node *y = x->right;
x->right = y->left;
// y的左子树不为空
if (y->left != T->nil) {
y->left->parent = x;
}
y->parent = x->parent;
if (x->parent == T->nil) {
// x的parent是根节点
T->root = y;
} else if (x == x->parent->left) {
// x是parent的左子树
x->parent->left = y;
} else {
// x是parent的右子树
x->parent->right = y;
}
y->left = x;
x->parent = y;
}
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
// nullptr --> T->nil
if (y == T->nil)
return;
rbtree_node *x = y->left;
y->left = x->right;
// x的左子树不为空
if (x->left != T->nil) {
x->left->parent = y;
}
x->parent = y->parent;
if (y->parent == T->nil) {
// y的parent是根节点
T->root = x;
} else if (y == y->parent->left) {
// y是parent的左子树
y->parent->left = x;
} else {
// y是parent的右子树
y->parent->right = x;
}
x->right = y;
y->parent = x;
}
#define RED 0
#define BLACK 0
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
// 若插入结点的父节点是黑色,则无需处理!
while (z->parent->color == RED)
// 插入结点的父节点是红色,需要处理!
{
if (z->parent == z->parent->parent->left) {
// 插入结点的父节点是祖父结点的左孩子
// 这里的y是叔叔节点
auto y = z->parent->parent->right;
// rbtree_node *y = z->parent->parent->right;
// 叔结点是红色的
if (y->color == RED) {
z->parent->color = BLACK;
z->parent->parent->color = RED;
y->color = BLACK;
// 从祖父节点开始迭代。
z = z->parent->parent;
} else {
// 特殊情况
// 叔结点是黑色的,
if (z == z->parent->right) {
z = z->parent;
rbtree_left_rotate(T, z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
} else {
auto 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;
} else {
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}
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;
// 类似avl,查找到具体插入点的位置
// 等于叶子结点时退出循环
while (x != T->nil) {
// 留一个y,保留x的父节点
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;
if (y == T->nil) {
T->root = z;
} else if (z->key < y->key) {
// z在左子树
// z->parent->left = z;
y->left = z;
} else {
y->right = z;
}
// 增加的节点都是默认红色的
z->color = RED;
z->left = T->nil;
z->right = T->nil;
rbtree_insert_fixup(T, z);
}
void rbtree_delete_fixup(rbtree *T, rbtree_node *x) {
}
// 找后继,属于删除节点里的内容,未完待续……
rbtree_node *rbtree_successor(rbtree *T, rbtree_node *x) {
}
// 删除结点,未完待续……
rbtree_node *rbtree_delete(rbtree *T, rbtree_node *z) {
rbtree_node *x = T->nil;
// y是后继节点……
rbtree_node *y = T->nil;
if ((z->left == T->nil) || (z->right == T->nil)) {
y = z;
} else {
y = rbtree_successor(T, z);
}
if (y->color == BLACK) {
rbtree_delete_fixup(T, x);
}
}
技术参考
本文部分技术点出,Linux C/C++服务器直播视频:推荐免费订阅