Rbtree学习

rbtree的几个原则

  • 颜色属性)节点非黑即红
  • 根属性)根节点一定是黑色
  • 叶子属性)叶子节点(NIL)一定是黑色
  • 红色属性)每个红色节点的两个子节点,都为黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  • 黑色属性)从任一节点到其每个叶子的所有路径,都包含相同数目的黑色节点。

rbtree平衡调整的两个手段: 旋转 和 变色

rbtree的平衡调整需要关注两个场景:当父亲节点为红色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
叔叔节点为红:
即父亲叔叔都为红-》调整为黑,同时爷爷变红
爷爷向上递推调整

叔叔节点为黑:
1、LL型失衡:变色、对爷爷左旋或右旋
privot,root颜色取反,旋转

2、LR型失衡:对父亲旋转-》LL型

## 说白了即满足黑色属性和红色属性因此

# 平衡调整代码:
inline void
Rb_tree_rebalance(Rb_tree_node_base* __x, Rb_tree_node_base*& __root)
{
__x->_color = red;
// 隐藏条件 父节点红色祖父节点一定黑色
while (__x != __root && __x->_parent->_color == red)
{
/*
* 根节点一定是黑色
* 判断插入节点在左右
* 然后再判断父节点和叔叔节点是红色还是异色
* 红色直接变色
* 异色则考虑LR型,再变色
*/
if (__x->_parent == __x->_parent->_parent->_left)
{
Rb_tree_node_base* __y = __x->_parent->_parent->right;
if (__y && __y->_color == red)
{
__x->_parent->_color = black;
__y->_color = black;
__x->_parent->parent->_color = red;
__x = __x->_parent;
}
else
{
if (__x == __x->_parent->right)
{
__x == __x->_parent;
Rb_tree_rotate_left(__x, __root);
}

__x->_parent->_color = black;
__x->_parent->_parent->_color = red;
Rb_trer_rotate_right(__x->_parent->_parent, __root);
}
}
else {
Rb_tree_node_base* __y = __x->_parent->_parent->_left;
if (__y && __y-> _color == red)
{
__x->_parent->_color = black;
__y->_color = black;
__x->_parent->_parent->_color = red;
__x = __x->_parent->_parent;
}
else
{
if (__x == __x->_parent->_left)
{
__x = __x->_parent;
Rb_trer_rotate_right(__x, __root);
}
__x->_parent->_color = black;
__x->_parent->_parent->_color = red;
Rb_tree_rotate_left(__x->_parent->_parent, __root);
}
}
}
__root->_color = black;
}

由此可以很快得出插入、删除的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
将复杂问题拆分简单问题
先列举无孩子、只有一个孩子情况,再判断自己和兄弟颜色
*/

Rb_tree_node_base* erase_aux(Rb_tree_node_base* x, Rb_tree_node_base* root)
{
if(x->left == 0 && x->right == 0)
{ // 无孩子节点
if(x->color == red)
{
if(x == x->parent->left) x->parent->left = nullptr;
else x->parent->right = nullptr;
return x;
}
else // 兄弟必定存在,黑色属性
{
Rb_tree_node_base* p = x->parent;
Rb_tree_node_base* bro = p->left == x ? p->right : p->left;
if(bro->color == red)
{ // bro为红,则必有两个孩子节点都为黑,p为黑
if(bro == p->left)
{
bro->right->color = red;
bro->color = black;
rotate_right(p);
}
else
{
bro->left->color = red;
bro->color = black;
rotate_left(p);
}
}
else
{ // bro为黑
if(bro->left == 0 && bro->right == 0)
{
bro->color = red;
p->color = black;
}
else if(bro->right == 0) // bro只有一个红孩子
{
if(bro == p->left)
{
bro->left->color = black;
bro->color = p->color;
p->color = black;
rotate_right(p);
}
else(bro == p->right)
{ } // 同理
}
else if(bro->left == 0)
{
// 同理
}
else 有两个孩子,都为红色
{
if(bro == p->left)
{
bro->left->color = black;
bro->color = p->color;
p->color = black;
rotate_right(p);
}
else
{} // 同理
}
}
}
}
else if(x->right == 0) // x只有有左孩子
{ // 因为只有一个孩子,必为红色,直接删除
}
else // 只有有孩子,同理
{
}
}

// 最后若有两个孩子,则跟前驱后继的一个值对换,转换到上面的其中一种情况


常见面试题

BST和AVL区别:

BST没有自平衡,查找效率容易变成O(n),引入左右子树高度差保证O(logn)
有AVL为什么还要Rbtree:

AVL的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡
在频繁进行插入/删除的场景中,频繁的旋转操作使得AVL的性能大打折扣
红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,
整体性能优于AVL
红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
红黑树的红黑规则,保证最坏的情况下,也能在O ( log n)时间内完成查找操作。


AVL rbtree区别

1、调整平衡的实现机制不同

红黑树根据路径上黑色节点数目一致,来确定是否失衡,如果失衡,就通过变色和旋转来恢复

AVL根据树的平衡因子(所有节点的左右子树高度差的绝对值不超过1),来确定是否失衡,如果失衡,就通过旋转来恢复

2、红黑树的插入效率更高

红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,

红黑树并不追求“完全平衡”,它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能

而AVL是严格平衡树(高度平衡的二叉搜索树),因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。

所以红黑树的插入效率更高

3、红黑树统计性能比AVL树更高

红黑树能够以O(log n) 的时间复杂度进行查询、插入、删除操作。

AVL树查找、插入和删除在平均和最坏情况下都是O(log n)。

红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高,

4、适用性:AVL查找效率高

如果你的应用中,查询的次数远远大于插入和删除,那么选择AVL树,如果查询和插入删除次数几乎差不多,应选择红黑树。

即,有时仅为了排序(建立-遍历-删除),不查找或查找次数很少,R-B树合算一些