SGI STL 标准库 tree简介 RB-tree解析 7

github源码分析仓库

关联式容器


标准的STL关联式容器分为set(集合)和map(映射表)两大类,还有multiset(多键集合)和multimap(多键映射表)。set、map、multiset、multimap的底层都使用RB-tree完成,RB-tree是一个独立容器,但并不开放给外界使用。
SGI STL提供不在标准内的关联容器hash table(散列表),以hash table为底层容器完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多键集合)、hash_multimap(散列多键映射表)。
关联式容器:每个元素都有一个键值(key)和一个实值(value)。当元素被插入关联式容器中时,容器内部结构按照键值大小,以某种特定规则将该元素放置于适当位置。
关联式容器的内部结构一般是balanced binary tree(平衡二叉树),获得良好的搜寻效率。balanced binary tree有多种类型,AVL-tree、RB-tree、AA-tree,STL运用的是RB-tree(红黑树)。


树由节点(nodes)和边(edges)构成,整棵树最顶端节点称为根节点。每个节点有具有方向性的边(directed edges),用来和其它节点相连。相连节点之中,在上方者为父节点(parent),下方者子节点(child),无子节点者称为叶节点(leaf)。子节点可以存在多个,如果最多只允许两个子节点,即所谓二叉树(binary tree)。不同的节点如果拥有相同的父节点,则彼此互为兄弟节点(siblings)。根节点至任何节点之间有唯一路径(path),路径所经过的边数,称为路径长度。根节点至任一节点的路径长度即该节点的深度(depth)。根节点的深度永远为0,。某节点至其最深子节点(叶节点)的路径长度,称为该节点的高度(height)。这棵树的高度以根节点的高度来表示。如果节点A->B之间存在唯一路径,A称为B的祖代(ancestor),B称为A的子代(descendant)。任何节点的大小(size)是指其所有子代(包括自己)的节点总数。
树状结构的相关术语

二叉搜索树


二叉树是指任何节点最多只允许两个子节点的树。两个子节点称为左子节点和右子节点。
二叉搜索树提供对数时间的元素插入和访问,二叉搜索树的节点放置规则:任何检点的键值一定大于其左子树的每个节点的键值,并小于其右子树的每一个节点的键值。因此,从根节点一直向左走,直至左路可走,即得最小元素;从根节点一直向右走,直至无右路可走,即得最大元素。
二叉搜索树插入新元素,从根节点开始,遇到键值较大者就向左,遇到键值较小值就向右,一直到尾端,即为插入点。
二叉搜索树移除旧节点A

  1. 如果A只有一个子节点或A为叶节点,直接将A的子节点连至A的父节点,并将A删除。
  2. 如果A有两个子节点,以右子树内的最小节点取代A。

平衡二叉搜索树


如果输入值不够随机,在经过某些插入和删除操作后,二叉搜索树可能失去平衡,造成搜索效率低落的情况。
不平衡二叉搜索树和平衡二叉搜索树
树的平衡没有绝对标准,大致意义:没有任何一个检点过深。

AVL tree(Adelson-Velskii-Landis tree)


AVL树是“加上额外平衡条件”的二叉搜索树。平衡条件的建立是为了确保整棵树的深度为O(logN)。AVL-tree要求任何节点的左右子树高度相差最多1。
AVL树插入节点可能会破坏AVL-tree的平衡条件,由于只有“插入点至根节点”路径上的各节点可能改变平衡条件,因此,只要调整其中最深的那个节点,便可使整棵树重新获得平衡。假设该最深节点为X,由于节点最多拥有两个子节点,“平衡被破坏”意味着X的左右两棵子树的高度相差2,因此有四种情况:

  1. 插入点位于X的左子节点的左子树——左左。
  2. 插入点位于X的左子节点的右子树——左右。
  3. 插入点位于X的右子节点的左子树——右左。
  4. 插入点位于X的右子节点的右子树——右右。

情况1、4彼此对称,称为外侧插入,采用单旋转操作调整解决。情况2、3彼此对称,称为内侧插入,采用双旋转操作调整解决。
AVL-tree四种"平衡破坏"情况

单旋转(Single Rotation)


AVL-tree恢复平衡单旋转

双旋转(Double Rotation)


AVL-tree恢复平衡双旋转

RB-tree红黑树


RB-tree不仅是二叉搜索树,而且必须满足以下规则:

  1. 每个节点不是红色就是黑色。
  2. 根节点为黑色。
  3. 如果节点为红色,其子节点必须为黑色。
  4. 任一节点到NULL(树尾端)的任何路径,所含黑节点数目必须相同。

根据规则4,新增节点必须为红色。根据规则3,新增节点父节点必须为黑色。如果根据二叉搜索树观测到达插入点,不满足上述条件,必须调整颜色并旋转树型。为了方便,将NULL视为黑节点。

插入节点


为特殊节点定义代名。新节点X,父节点P,祖父节点G,伯父节点S,增祖父节点GG。
根据二叉搜索树规则,新节点X必为叶节点。根绝红黑树规则4,X必为红色。若P也是红色(违反规则3,必须调整树型),则G必为黑色(因为原为RB-tree,必须遵循规则3)。根据X插入位置和外围节点(S和GG)的颜色,有四种情况:

  1. S为黑且X为外侧插入。先对P,G做一次单旋转,再更改P,G颜色,即可满足红黑树的规则3。
    红黑树插入情况1
  2. S为黑且X为内侧插入。先对P,X做一次单旋转并更改G、X颜色,在将结果对G做一次单旋转,集合再次满足红黑树规则3。
    红黑树插入情况2
  3. S为红色且X为外侧插入。先对P,G做一次单旋转,并改变X的颜色,如果GG为黑色,完成旋转修改。如果GG为红色,见状况4。
    红黑树插入情况3
  4. S为红色且X为外侧插入。先对P和G做一次单旋转,并改变X的颜色,如果GG为红色,持续向上做该操作,直到不再有父子连续为红色的情况。
    红黑树插入情况4

从上向下,避免状况4


为了避免状况4“父子节点皆为红色”的情况持续向RB-tree的上层结构发展,形成处理时效上的瓶颈,可以用从上而下的方式避免:假设新增节点A,沿着A的路径,只要看到某节点X的两个子节点皆为黑色,就把X改为红色,并把两个子节点改为黑色。
沿着X的路径,由上而下修正节点颜色
对G,P做一次右旋转,并改变颜色

RB-tree的节点设计


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
typedef bool _Rb_tree_Color_type;
const _Rb_tree_Color_type _S_rb_tree_red = false;
const _Rb_tree_Color_type _S_rb_tree_black = true;

struct _Rb_tree_node_base
{
typedef _Rb_tree_Color_type _Color_type;
typedef _Rb_tree_node_base* _Base_ptr;

_Color_type _M_color; //节点颜色,非红即黑
_Base_ptr _M_parent; //父节点
_Base_ptr _M_left; //左子节点
_Base_ptr _M_right; //右子节点

//一直向左走得到最小值
static _Base_ptr _S_minimum(_Base_ptr __x)
{
while (__x->_M_left != 0) __x = __x->_M_left;
return __x;
}

//一直向右走得到最大值
static _Base_ptr _S_maximum(_Base_ptr __x)
{
while (__x->_M_right != 0) __x = __x->_M_right;
return __x;
}
};

//RB-tree节点
template <class _Value>
struct _Rb_tree_node : public _Rb_tree_node_base
{
typedef _Rb_tree_node<_Value>* _Link_type;
_Value _M_value_field; //节点值
}

RB-tree的迭代器


RB-tree的节点和迭代器之间的关系
RB-tree的迭代器属于双向迭代器。

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
83
84
85
86
87
//基层迭代器
struct _Rb_tree_base_iterator
{
typedef _Rb_tree_node_base::_Base_ptr _Base_ptr;
typedef bidirectional_iterator_tag iterator_category; //双向迭代器
typedef ptrdiff_t difference_type;
_Base_ptr _M_node;

//用于实现operator++
void _M_increment()
{
if (_M_node->_M_right != 0) { //如果有右节点,状况1
_M_node = _M_node->_M_right; //向右走
while (_M_node->_M_left != 0) //然后一直往左子数走到底
_M_node = _M_node->_M_left;
}
else { //没有右子节点,状况2
_Base_ptr __y = _M_node->_M_parent; //找出父节点
while (_M_node == __y->_M_right) { //如果现行节点本身是个右节点
_M_node = __y; //一直上溯,直到"不为右节点"为止
__y = __y->_M_parent;
}
if (_M_node->_M_right != __y) //如果右子节点不等于此时的父节点
_M_node = __y; //则返回此时的父节点,状况3,否则返回此时的_M_node节点,状况4
}
}

//用来实现operator--
void _M_decrement()
{
if (_M_node->_M_color == _S_rb_tree_red &&
_M_node->_M_parent->_M_parent == _M_node) //如果是红节点,且父节点的父节点等于自己,
_M_node = _M_node->_M_right; //状况1,右子节点即为返回值
else if (_M_node->_M_left != 0) { //如果有左子节点,状况2
_Base_ptr __y = _M_node->_M_left; //另y指向左子节点
while (__y->_M_right != 0) //当y有右子节点时
__y = __y->_M_right; //一直往右子节点走到底
_M_node = __y; //即得最后答案
}
else { //即非根节点,也不是左子节点
_Base_ptr __y = _M_node->_M_parent; //状况3,找到父节点
while (_M_node == __y->_M_left) { //当现行节点身为左子节点,
_M_node = __y; //一直往上交替走,直到现行节点
__y = __y->_M_parent; //不为左子节点
}
_M_node = __y; //此时父节点即为答案
}
}
};
// RB-tree 迭代器
template <class _Value, class _Ref, class _Ptr>
struct _Rb_tree_iterator : public _Rb_tree_base_iterator
{
typedef _Value value_type;
typedef _Ref reference;
typedef _Ptr pointer;
typedef _Rb_tree_iterator<_Value, _Value&, _Value*>
iterator;
typedef _Rb_tree_iterator<_Value, const _Value&, const _Value*>
const_iterator;
typedef _Rb_tree_iterator<_Value, _Ref, _Ptr>
_Self;
typedef _Rb_tree_node<_Value>* _Link_type;

_Rb_tree_iterator() {}
_Rb_tree_iterator(_Link_type __x) { _M_node = __x; }
_Rb_tree_iterator(const iterator& __it) { _M_node = __it._M_node; }

reference operator*() const { return _Link_type(_M_node)->_M_value_field; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */

_Self& operator++() { _M_increment(); return *this; }
_Self operator++(int) {
_Self __tmp = *this;
_M_increment();
return __tmp;
}

_Self& operator--() { _M_decrement(); return *this; }
_Self operator--(int) {
_Self __tmp = *this;
_M_decrement();
return __tmp;
}
};

RB-tree的数据结构 RB-tree的构造与内存管理 RB-tree的元素操作


为了简化根节点边界条件的处理,SGISTL特别为根节点再设计了一个父节点,名为header。
header和root
RB-tree有两种插入操作:insert_unique()insert_equal()insert_unique()表示被插入节点的键值在整棵树中必须独一无二(因此,如果树中已存在相同的键值,插入操作就不会被真正的进行),insert_equal()表示被插入节点的键值在整棵树中可以重复。

依次将10,7,8,15,5,6,11,13,12插入RB-tree
将元素插入红黑树的全过程演示

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//参数__x为新值插入点,参数__y为插入点之父节点,参数v为新值
template <class _Key, class _Value, class _KeyOfValue,
class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
::_M_insert(_Base_ptr __x_, _Base_ptr __y_, const _Value& __v)
{
_Link_type __x = (_Link_type) __x_;
_Link_type __y = (_Link_type) __y_;
_Link_type __z;

if (__y == _M_header || __x != 0 ||
_M_key_compare(_KeyOfValue()(__v), _S_key(__y))) {
__z = _M_create_node(__v); //产生新节点
_S_left(__y) = __z; //当y即为header时,leftmost() == z
if (__y == _M_header) {
_M_root() = __z;
_M_rightmost() = __z;
}
else if (__y == _M_leftmost()) //如果y为最左节点
_M_leftmost() = __z; //维护_M_leftmost(),使其永远指向最左节点
}
else {
__z = _M_create_node(__v); //产生新节点
_S_right(__y) = __z; //令新节点成为插入点之父节点y的右子节点
if (__y == _M_rightmost())
_M_rightmost() = __z; //维护_M_rightmost(),使其永远指向最右节点
}
_S_parent(__z) = __y; //设定新节点的父节点
_S_left(__z) = 0; //设定新节点的左子节点
_S_right(__z) = 0; //设定新节点的右子节点
//新节点颜色在_Rb_tree_rebalance()设定并调整 参数1为新增节点 参数2为root
_Rb_tree_rebalance(__z, _M_header->_M_parent);
++_M_node_count; //节点值累加
return iterator(__z);
}

//全局函数,令树形平衡(改变颜色及旋转树形)
//__x为新增节点 __root为root
inline void
_Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
__x->_M_color = _S_rb_tree_red; //新节点必为红
while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) { //父节点必为红
if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) { //父节点为祖父节点之左子节点
_Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right; //令y为伯父节点
if (__y && __y->_M_color == _S_rb_tree_red) { //伯父节点存在,且为红
__x->_M_parent->_M_color = _S_rb_tree_black; //更改父节点为黑
__y->_M_color = _S_rb_tree_black; //更改伯父节点为黑
__x->_M_parent->_M_parent->_M_color = _S_rb_tree_red; //更改祖父节点为红
__x = __x->_M_parent->_M_parent;
}
else { //无伯父节点,或伯父节点为黑
if (__x == __x->_M_parent->_M_right) { //如果新节点为父节点之右节点
__x = __x->_M_parent;
_Rb_tree_rotate_left(__x, __root); //第一个参数为左旋点
}
__x->_M_parent->_M_color = _S_rb_tree_black; //改变颜色
__x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
_Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root); //第一个参数为右旋点
}
}
else { //父节点为祖父节点之右子节点
_Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left; //令y为伯父节点
if (__y && __y->_M_color == _S_rb_tree_red) { //右伯父节点,且为红
__x->_M_parent->_M_color = _S_rb_tree_black; //更改父节点为黑
__y->_M_color = _S_rb_tree_black; //更改伯父节点为黑
__x->_M_parent->_M_parent->_M_color = _S_rb_tree_red; //更改祖父节点为红
__x = __x->_M_parent->_M_parent; //继续往上层查
}
else { //无伯父节点,或伯父节点为黑
if (__x == __x->_M_parent->_M_left) { //如果新节点为父节点之左子节点
__x = __x->_M_parent;
_Rb_tree_rotate_right(__x, __root); //第一个参数为右旋点
}
__x->_M_parent->_M_color = _S_rb_tree_black; //改变颜色
__x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
_Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root); //第一个参数为左旋点
}
}
} //while 结束
__root->_M_color = _S_rb_tree_black; //根节点永远为黑
}

//全局函数,新节点必为红节点,如果插入处的父节点也是红节点,违反红黑树规则,必须旋转树形
inline void
_Rb_tree_rotate_left(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
//__x为旋转点
_Rb_tree_node_base* __y = __x->_M_right; //令__y为旋转点的右子节点
__x->_M_right = __y->_M_left;
if (__y->_M_left !=0)
__y->_M_left->_M_parent = __x; //设定父节点
__y->_M_parent = __x->_M_parent;

//令__y完全顶替__x的地位
if (__x == __root) //__x为根节点
__root = __y;
else if (__x == __x->_M_parent->_M_left) //__x为父节点的左子节点
__x->_M_parent->_M_left = __y;
else //__x为父节点的右子节点
__x->_M_parent->_M_right = __y;
__y->_M_left = __x;
__x->_M_parent = __y;
}

//全局函数,新节点必为红节点,如果插入处的父节点也是红节点,违反红黑树规则,必须旋转树形
inline void
_Rb_tree_rotate_right(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
//__x为旋转点
_Rb_tree_node_base* __y = __x->_M_left; //令__y为旋转点的左子节点
__x->_M_left = __y->_M_right;
if (__y->_M_right != 0)
__y->_M_right->_M_parent = __x; //设定父节点
__y->_M_parent = __x->_M_parent;

//令__y完全顶替__x的地位
if (__x == __root) //__x为根节点
__root = __y;
else if (__x == __x->_M_parent->_M_right) //__x为父节点的右子节点
__x->_M_parent->_M_right = __y;
else //__x为父节点的左子节点
__x->_M_parent->_M_left = __y;
__y->_M_right = __x;
__x->_M_parent = __y;
}