红黑树
说明:
图片信息来源于计蒜客数据结构红黑树一讲
注意自己动手多画
介绍
红黑树是一种自平衡二查查找树,是由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。
红黑树与AVL
相比,他牺牲了部分平衡性以在插入和删除操作时,减少旋转操作.
红黑树与其他平衡二叉树相比,他增加了一个颜色属性,用来标识节点是红色还是黑色,如果一个节点没有子节点,则该节点的子节点对应的指针为NIL
,也就是说红黑树的所有叶子节点都是NIL
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
C++STL中的map,set就是用红黑树实现的
五个条件
- 每个节点非黑即红
- 根节点是黑色
- 叶节点(NIL)是黑色
- 如果一个节点是红色,则他的两个子节点都是黑色
- 从根节点出发到所有叶节点路径上,黑色节点数量相同
调整策略
- 插入调整站在祖父节点看,调整两红
- 删除调整站在父节点看,调整双重黑
- 插入和删除的情况处理一共五种
学习前提:
需要有AVL树的基础.
插入调整
分析:
插入的时候,我们初始化把节点初始化为红色.这样插入在违反规则上只用考虑双红的情况,并且只从祖父节点忘下看,父亲节点和孙子节点节点时候违反规则.
提问一:为什么要插入的新节点为红色?
如果我们要插入的新节点为黑色,某条支路上会多个红色,会使得第五条规则被破坏,很难通过旋转来调整.而通过旋转可以比较容易的使得红色节点分开
提问二:为什么要从祖父节点忘下看?而不能从父亲节点往下看?
这个问题其实是由于调整引起的.我们需要三层节点,而两层节点无法完成调整.
试想一下,我们从父亲节点忘下看,父亲节点与儿子节点发生双红,另一个儿子一定为黑色,我们无论是左旋,还是右旋都无法满足要求,所以只考虑两层是无法满足要求的,所以要考虑三层,我们接下来的分析就是考虑三层的情况下,完成调整的
情况一:叔父节点是红色
如果叔父节点是红色那么祖父节点一定是黑色,并且这个节点的父亲节点和本节点都是红色,我们只需要将祖父节点变成红色,祖父节点的两个儿子节点都变成黑色即可(所谓的红色上顶)
情况二:叔父节点是黑色
如果叔父节点是黑色,因为父亲节点为红色所以祖父节点一定是黑色,这时有两种情况,一种是本节点和父亲节点属于同一侧的孩子节点,另一中是属于不同侧的孩子节点.
不同侧
如果父亲是左孩子,本节点是右孩子.来一个小左旋,现在红色节点都是左孩子了,就进行同侧的处理方案,反之同样分析
同侧
如果都是父亲节点与本节点左孩子,直接一个大右旋将,这时右支路少一个黑色因为祖父节点原来为黑,现在变成黑色了,其他的没有改变,有两种调整方案,一种是红色上顶(祖父左右孩子编程黑色,祖父变成红色),一种是红色下沉(祖父变成黑色,左右孩子变成红色)
删除调整
分析:
删除调整,如果我们删除的是一个红色节点,则不会影响红黑树的性质,直接删除即可
如果我们删除的是一个黑色节点,则会影响红黑树的性质,则需要分析.
- 如果我们要删除的时是有两个孩子的节点,我们会去找到他的前驱节点,就变成了有一个孩子节点的节点
- 如果我们删除的是没有孩子节点的节点,相应的他的左右孩子都是
NIL
我们就利用NIL
当成一个孩子节点,也变成了只有一个孩子节点的情况 - 如果我们删除的是有一个孩子节点的,可以分为一下几种情况,我们具体分析
且我们在删除黑色节点时,为了不影响红黑树的性质,我们是将响应的节点加上一重黑,如果响应节点已经是黑色节点就变成了双重黑,如果相应节点是红色节点,则不违反红黑树的规则
注意:经过上面的分析,我们下面处理的情况都是需删除节点是黑色,且有一条支路的情况,我们删除这个节点且让其子孩子加上一重黑,这时有两种情况,一种是出现双重黑,进行调整,另一种是孩子节点是红色,没有出现双重黑,不用进行调整,我们下面都是出现双重黑的结果
提问-:为什么可以将NIL
当成一个孩子处理?
其实NIL
可以当成一个孩子处理的必要条件是,我们在后续处理中其他支路没有用到NIL
,后面的具体分析中可以慢慢体悟
提问二:为什么删除的节点只需要两层?
因为我们删除的情况如果两层无法解决,就是一直向上递归返回,并进行处理,所以两层就可以解决
情况一
兄弟节点为红色,父亲节点一定是黑色,将父亲节点进行一次旋转(旋转之后兄弟节点变成跟节点),但是原兄弟节点支路少了一个黑丝节点,所以将原兄弟节点与原跟节点颜色互换即可.
情况二
如果兄弟节点的两个孩子节点都是黑色,兄弟节点变成红色,x
减少一重黑,父亲节点增加一重黑,向上返回
情况三
这时分为兄弟节点有一个红色节点,或者有两个红色节点,但是有两个红色节点不影响我们后续的操作,所以我们只讨论有一个红色节点的情况
不同侧有红色节点
交换父兄弟节点与红色节点的颜色,并将原红色节点旋转为成为brother节点,这样就变成同侧是红色了,进行同侧处理
同侧有红色节点(两红归为这一类)
如果是RR型就进行,首先双重黑节点减去一重黑,然后进行大左旋,交换原父亲节点与兄弟节点的颜色即可
超级超级简短的代码150行(红色上顶)
/*************************************************************************
> File Name: 5.rbt.cpp
> Author: ldc
> Mail: litesla
> Created Time: 2019年02月22日 星期五 20时49分32秒
************************************************************************/
//0 red 1 black 2 double_dlack
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct Node{
int key,color;
struct Node *lchild, *rchild;
}Node;
Node *NIL;
__attribute__((constructor))
void init_NIL(){
NIL = (Node*)malloc(sizeof(Node));
NIL->key =-1;
NIL->color = 1;
NIL->lchild = NIL->rchild = NIL;
}
Node *init(int key) {
Node *p = (Node *)malloc(sizeof(Node));
p->key = key;
p->color = 0;
p->lchild = p->rchild = NIL;
return p;
}
int has_red_child(Node *node) {
return (node->lchild->color == 0 || node->rchild->color == 0);
}
Node *left_rotate(Node *node) {
Node *temp = node->rchild;
node->rchild = temp->lchild;
temp->lchild = node;
return temp;
}
Node *right_rotate(Node *node) {
Node *temp = node->lchild;
node->lchild = temp->rchild;
temp->rchild = node;
return temp;
}
Node *insert_maintain(Node *node) {
if (!has_red_child(node)) return node;
else if (node->lchild->color == 0 && node->rchild->color == 0) {
if (!has_red_child(node->lchild) && !has_red_child(node->rchild)) return node;
}else if (node->lchild->color == 0 && has_red_child(node->lchild)){
if (node->lchild->rchild->color == 0) {
node->lchild = left_rotate(node->lchild);
}
node = right_rotate(node);
}else if (node->rchild->color == 0 && has_red_child(node->rchild)) {
if (node->rchild->lchild->color == 0) {
node->rchild = right_rotate(node->rchild);
}
node = left_rotate(node);
} else {
return node;
}
node->color = 0;
node->lchild->color = node->rchild->color = 1;
return node;
}
Node *__insert(Node *node, int key) {
if (node == NIL) return init(key);
else if (node->key == key) return node;
else if (node->key > key) node->lchild = __insert(node->lchild, key);
else node->rchild = __insert(node->rchild, key);
return insert_maintain(node);
}
Node *insert(Node *node, int key){
node = __insert(node, key);
node->color = 1;
return node;
}
Node *erase_maintain(Node *node) {
#define maintain(a,b,c,d) ({\
if (node->b->color == 0) {\
node = c(node);\
node->color = 1;\
node->a->color = 0;\
node->a = erase_maintain(node->a);\
return node;\
}\
node->a->color = 1;\
if (!has_red_child(node->b)) {\
node->color += 1;\
node->b->color -= 1;\
return node;\
}else if (node->b->b->color != 0) {\
node->b = d(node->b);\
node->b->color = 1;\
node->b->b->color = 0;\
}\
node = c(node);\
node->color = node->a->color;\
node->lchild->color = node->rchild->color = 1;\
})
if (node->lchild->color != 2 && node->rchild->color != 2) return node;
else if (node->lchild->color == 2){
maintain(lchild, rchild, left_rotate, right_rotate);
}else {
maintain(rchild, lchild, right_rotate, left_rotate);
}
return node;
}
Node *pressor(Node *node) {
Node *temp = node->lchild;
while (temp->rchild != NIL) temp = temp->rchild;
return temp;
}
Node *__erase(Node *node, int key) {
if (node == NIL) return node;
else if (node->key > key) node->lchild = __erase(node->lchild, key);
else if (node->key < key) node->rchild = __erase(node->rchild, key);
else {
if (node->lchild == NIL || node->rchild == NIL) {
Node *temp = (node->lchild != NIL ? node->lchild : node->rchild);
temp->color += node->color;
free(node);
return temp;
}else {
Node *temp = pressor(node);
node->key = temp->key;
node->lchild = __erase(node->lchild, temp->key);
}
}
return erase_maintain(node);
}
Node *erase(Node *node, int key) {
node = __erase(node,key);
node->color = 1;
return node;
}
void clear(Node *node) {
if (node == NIL) return ;
clear(node->lchild);
clear(node->rchild);
free(node);
return ;
}
int main() {
return 0;
}