红黑树的概念
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
红黑树的性质
- 每个结点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子结点是黑色的
- 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
- 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
这些性质确保了红黑树的平衡性,从而限制了树的高度,使得基本操作的时间复杂度保持在 O(logn)。
思考:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?
-
性质分析:
- 性质1和性质3确保了红色节点不会相邻,且红色节点的子节点必须是黑色的。这有助于限制红色节点在树中的分布,从而限制树的高度增长。
- 性质2和性质5指出根节点和叶子节点(空节点)都是黑色的,这提供了一个基本的黑色节点数量标准。
- 性质4是红黑树保持平衡的关键。它确保了从根到任何叶子节点的所有简单路径上,黑色节点的数量是相同的。这意味着树的高度是固定的,而红色节点只是在这个基础上增加了额外的层。
-
路径长度分析:
- 假设从根到叶子的最长路径上,黑色节点的数量为
h
- 由于红色节点不能相邻,且每个红色节点下面至少有两个黑色节点(其子节点),因此最长路径(包含红色和黑色节点)上红色节点的数量最多为
h-1
(因为根是黑色的,所以路径开始时不计算红色节点)。 - 因此,最长路径上的节点总数最多为
2h - 1
(h
个黑色节点加上最多h-1
个红色节点)。 - 最短路径(全为黑色节点)上的节点数量为
h
。
- 假设从根到叶子的最长路径上,黑色节点的数量为
-
最长路径与最短路径的比较:
- 最长路径上的节点数
2h - 1
与最短路径上的节点数h
的比值为(2h - 1) / h
,简化后为2 - 1/h
。 - 由于
h
是整数且h >= 1
(因为至少有一个黑色节点,即根节点),所以1/h
的值在(0, 1]范围内。 - 因此,
(2 - 1/h)
的值总是小于或等于2,即最长路径上的节点数不会超过最短路径节点数的两倍。
- 最长路径上的节点数
红黑树节点的定义
// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _color(color)
{}
RBTreeNode<ValueType>* _pLeft; // 节点的左孩子
RBTreeNode<ValueType>* _pRight; // 节点的右孩子
RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)
ValueType _data; // 节点的值域
Color _color; // 节点的颜色
};
思考:在节点的定义中,为什么要将节点的默认颜色给成红色的?
不能有两个连续的红色节点。如果我们将新插入的节点默认设置为黑色,那么插入操作可能会导致树中违反这一性质的情况。通过将新节点设置为红色,我们可以更方便地进行调整,而不会破坏树的黑色节点平衡。
插入操作
在红黑树中插入节点的过程分为两个步骤:
标准二叉搜索树插入:首先按照二叉搜索树的插入规则将新节点插入树中,并将新节点着色为红色。
调整树:通过颜色翻转和旋转操作来恢复红黑树的性质。
以下是插入操作的详细步骤:
- 按照二叉搜索的树规则插入新节点
- 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
情况一: cur为红,p为红,g为黑,u存在且为红
情况二: 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
针对每种情况进行相应的处理即可。