红黑树,是一种二叉搜索树,但在每个结点增加一个存储位表示结点的颜色,可以是Red和Black,通过对任何一条从根到叶子结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
红黑树结点的定义
1 | // 节点的颜色 |
在定义节点时将节点的默认颜色给成红色的,是因为插入之前所有根至外部节点的路径上黑色节点数目都相同,所以如果插入的节点是黑色肯定错误(黑色节点数目不相同),而相对的插入红节点可能会也可能不会违反“没有连续两个节点是红色”这一条件,所以插入的节点为红色,如果违反条件再调整
红黑树结构
为了能够简单的实现后续的关联式容器,所以我们在红黑树中增加一个头节点,因为根节点必须是黑色的,为了与根节点进行区分,将头结点给成红色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点
1 | template<class ValueType> |
红黑树的插入
按照二叉搜索的树规则插入新节点
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
82template<class ValueType>
class RBTree
{
//……
bool Insert(const ValueType& data)
{
PNode& pRoot = GetRoot();
// 1. 按照二叉搜索的树方式插入新节点
if (nullptr == pRoot)
{
pRoot = new Node(data, BLACK);
// 根的双亲为头节点
pRoot->_pParent = _pHead;
}
else
{
// 按照二叉搜索树的特性,找节点在红黑树中的插入位置
PNode pCur = pRoot;
PNode pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (data < pCur->_data)
pCur = pCur->_pLeft;
else if (data > pCur->_data)
pCur = pCur->_pRight;
else
return false;
}
// 插入新节点
pCur = new Node(data);
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
// 更新新节点的双亲
pCur->_pParent = pParent;
// 2. 检测新节点插入后,红黑树的性质是否造到破坏,
// 若满足直接退出,否则对红黑树进行旋转着色处理
//...
}
// 根节点的颜色可能被修改,将其改回黑色
pRoot->_color = BLACK;
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
return true;
}
private:
PNode& GetRoot()
{
return _pHead->_pParent;
}
// 获取红黑树中最小节点,即最左侧节点
PNode LeftMost()
{
PNode pCur = GetRoot();
if (nullptr == pCur)
return _pHead;
// 获取红黑树中最左侧节点
while (pCur->_pLeft)
{
pCur = pCur->_pLeft;
}
return pCur;
}
// 获取红黑树中最大节点,即最右侧节点
PNode RightMost()
{
PNode pCur = GetRoot();
if (nullptr == pCur)
return _pHead;
// 获取红黑树中最右侧节点
while (pCur->_pRight)
{
pCur = pCur->_pRight;
}
return pCur;
}
private:
PNode _pHead;
};
- 检测新节点插入后,红黑树的性质是否造到破坏因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
情况一: cur为红,p为红,g为黑,u存在且为红
cur和p均为红,违反了性质三,此处能否将p直接改为黑?
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整情况二: cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色–p变黑,g变红
- 情况三: cur为红,p为红,g为黑,u不存在/u为黑
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2
1 | bool Insert(const ValueType& data) |
红黑树的验证
红黑树的检测分为两步:
- 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
- 检测其是否满足红黑树的性质
1 | bool IsValidRBTree() |
红黑树与AVL树的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( ),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。