注意:对红黑树操作时,要时刻谨记它原本已经是一棵红黑树,满足红黑树的所有性质
红黑树的定义与性质
- 1.每个结点是红的或者黑的
- 2.根结点是黑的
- 3.每个叶子结点是黑的
- 4.如果一个结点是红的,则它的两个儿子都是黑的
- 5.对每个结点,从该结点到其子孙结点的所有路径上的包含相同数目的黑结点
红黑树性质鉴定
判断下图那些为红黑树
图1不是:从14---->8---->叶结点 14----->8----->10----->叶结点 黑结点的数目不相等
图2是
图3不是:原因同图1
图4不是:根结点不是黑色的
红黑树的结构体定义
红黑树要比二叉搜索树多一个颜色属性
同时,为了方便确认插入位置,还可以多一个parent属性,用于表示当前节点的父节点
typedef int KEY_TYPE; //避免key的类型写死
typedef struct _rbtree_node {
unsigned char color;
struct _rbtree_node *right;//右子树
struct _rbtree_node *left;//左子树
struct _rbtree_node *parent;//为了调整时使用
KEY_TYPE key;
void *value;//4字节
} rbtree_node;
typedef struct _rbtree {
rbtree_node *root; //根节点
rbtree_node *nil; //所有的叶子节点
} rbtree;
红黑树的左旋与右旋
左旋操作
左旋:对X节点左旋,即以X的右孩子Y为轴,将X节点转下来,变为Y的左孩子,
简单记为左旋即把该节点变为左孩子。
注意2个问题:
1.既然要左旋要以右孩子为轴,那么右孩子是必须存在的,即不能为空。
2.左旋后y的左指针指向x,那原来y的左孩子b怎么办,谁去指向它?
x原来的右指针指向y,在左旋后该怎么办,它指向谁?
所以我们将左旋后x的右指针指向y原来的左孩子b即可。
/*三断开三连接 三个方向每个方向两根 需要改变6个指针*/
//左旋
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; //1 1 b指向x右
if (y->left != T->nil) {
//1 2
y->left->parent = x;
}
//改变x的父节点的指向
y->parent = x->parent; //1 3
if (x->parent == T->nil) {
//1 4 x是根的情况
T->root = y;
}
//如果x是左子树
else if (x == x->parent->left) {
x->parent->left = y;
}
//如果x是右子树
else {
x->parent->right = y;
}
//旋转后y的left为x
y->left = x; //1 5
x->parent = y; //1 6
}
右旋操作
右旋:对X节点右旋,即以X的左孩子Y为轴,将X节点转下来,变为Y的右孩子,
简单记为右旋即把该节点变为右孩子。
注意2个问题:
1.既然要右旋要以左孩子为轴,那么左孩子是必须存在的,即不能为空。
2.右旋后x的右指针指向y,那原来x的右孩子b怎么办,谁去指向它?
y原来的左指针指向x,在左旋后该怎么办,它指向谁?
所以我们将右旋后y的左指针指向x原来的右孩子b即可。
void rbtree_right_rotate(rbtree *T, rbtree_node *y) {
rbtree_node *x = y->left;
//改变y的左节点的指向
y->left = x->right;
if (x->right != T->nil) {
x->right->parent = y;
}
//改变y的父节点的指向
x->parent = y->parent;
if (y->parent == T->nil) {
T->root = x;
} else if (y == y->parent->right) {
y->parent->right = x;
} else {
y->parent->left = x;
}
//旋转后x的右节点变为y
x->right = y;
y->parent = x;
}
红黑树插入的实现
红黑树插入节点过程大致分析:
- RBTree为二叉搜索树,我们按照二叉搜索树的方法对其进行节点插入
- RBTree有颜色约束性质,因此我们在插入新节点之后要进行颜色调整
具体步骤如下:
- 根节点为NULL,直接插入新节点并将其颜色置为黑色
- 根节点不为NULL,找到要插入新节点的位置
- 插入新节点
- 判断新插入节点对全树颜色的影响,更新调整颜色
首先红黑树的插入其实不是那么容易实现的,以前搜索树的插入我们很容易理解现在我们首先思考一个问题,你插入节点的默认颜色是RED或BLACK? 这里我们需要根据性质来思考,首先如果插入黑节点,这个可以直接插入无论它的父亲是什么颜色,但是红黑树的性质是每条路径的黑色节点数目相同这个时候你再想想那其他路径的黑色节点数目一定比你现在少一个节点,所以调整起来是非常繁琐的. 插入红节点不需要调整其他路径,如果它的父亲为黑,那么直接插入,如果他的父亲为红那么在该路径上面开始分情况调整. 所以插入节点默认颜色一定要为红.如果为黑调节成本太大了.
因为在插入结点之前这已经是一颗红黑树(时刻紧扣这一点,非常重要对于理解)了,所以如果其父节点是红色,
则其祖父节点是黑色,叔父节点不确定,需要讨论,不同颜色,情况不一样。
接下来开始插入节点如果插入节点的父亲为黑那么直接插入后返回不需要做任何调整. 但是如果插入节点的父亲为红,那么就需要调整了.具体的调整过程可以分为三个情况:
第一种情况:
叔父节点是红色的,因其没插入之前原本就是一颗红黑树,因此父节点也为红色(不然不满足性质5),祖父节点是黑色的(不然不满足性质4)
此时对该子树进行操作parent节点和uncle节点变为黑,pParent节点变为红,这样我们就保证该子树中每条路径中黑色节点相同并且没有连续的红节点,然后再让cur等于pParent节点继续往上继续调整。
此时插入节点的父节点是左子节点和右子节点的不同,需要分开写,不过操作都一样
第二种情况:
叔结点是黑色的,而且当前插入结点是左孩子(对于父节点和祖父节点的颜色判断依旧通过性质)
cur为红,parent为红,pParent为黑,uncle不存在/u为黑
parent为pParent的左孩子,cur为parent的左孩子,则进行右单旋转;
uncle不存在:
uncle存在且为黑:
第三种情况:
叔结点是黑色的,而且当前结点是右孩子
cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转
则转换成了情况2
uncle不存在:
uncle存在且为黑:
调整的代码中1和4 、 2和6 、 3和5是一回事,只不过在实现代码时,对插入节点的父节点是左子节点还是右子节点进行判断,在实现的时候分开了,但是对应的操作是一样的
void rbtree_insert_fixup(rbtree *T, rbtree_node *z) {
while (z->parent->color == RED) {
//z ---> RED
//如果z的父节点是左子树的节点
if (z->parent == z->parent->parent->left) {
//y为z父节点的堂兄弟
rbtree_node *y = z->parent->parent->right;
// 1
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
//回溯
z = z->parent->parent; //z --> RED
}
//
else {
//2
if (z == z->parent->right) {
z = z->parent;
rbtree_left_rotate(T, z);
}
//3
z->parent->color = BLACK;
z->parent->parent->color = RED;
rbtree_right_rotate(T, z->parent->parent);
}
}
else {
rbtree_node *y = z->parent->parent->left;
//4
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent; //z --> RED
}
else {
//5
if (z == z->parent->left) {
z = z->parent;
rbtree_right_rotate(T, z);
}
//6
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;
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);
}
参考:https://course.0voice.com/v1/course/intro?courseId=5&agentId=0