红黑树作为一种自平衡的二叉搜索树,是计算机科学中的经典数据结构之一,广泛应用于各种需要高效查找、插入和删除操作的场景中,比如STL中的 map
和 set
。虽然它的基本原理并不复杂,但在实现过程中,需要处理许多细节,比如颜色的调整、旋转操作等。本文将对红黑树的结构、插入、删除等操作进行详细的剖析,以帮助大家更好地理解和实现这一高效的数据结构。
一、红黑树的基本概念与规则
红黑树是一种满足特定性质的二叉搜索树。与普通的二叉搜索树不同,红黑树的每个结点都附加了一个颜色属性,且通过一些规则保证了树的平衡性。红黑树的四条基本规则如下:
- 每个节点的颜色要么是红色,要么是黑色。
- 根节点是黑色的。
- 如果一个节点是红色的,那么它的两个子节点必须是黑色的。 这就意味着红色节点不能有连续的红色节点。
- 从任意一个节点到其所有叶子节点的路径上,必须包含相同数量的黑色节点。 这条规则保证了树的平衡性,防止树的某些路径过长。
红黑树的这些规则通过"颜色约束"来间接实现自平衡,因此每次进行插入、删除操作时,都需要确保这些规则被满足。


以上均为红黑树示例。
二、红黑树的高度与效率
红黑树的高度是它的关键特性之一。红黑树的自平衡机制确保了它的高度不会太大,这对于树的查找、插入和删除操作的效率至关重要。理论上,红黑树的高度 h 与黑色高度 bh(从根到叶子节点的路径上黑色节点的数量)之间存在如下关系:
- 最短路径(只有黑色节点)长度为 bh。
- 最长路径(交替的红色和黑色节点)长度为 2 * bh。
因此,红黑树的最大高度为 2 * bh,而最小高度为 bh。因此,红黑树的高度 h 满足 h≤2×bhh \leq 2 \times bhh≤2×bh,并且由于红黑树的黑色高度是相同的,所以可以得出红黑树的高度是 O(log N),其中 N 是树中结点的数量。

由于红黑树的高度被控制在对数级别,它能够保证查找、插入、删除操作在最坏情况下的时间复杂度均为 O(log N)。
三、红黑树的结构
红黑树的基本节点结构包括以下几个部分:
cpp
enum Colour {
RED,
BLACK
};
template<class K, class V>
struct RBTreeNode {
pair<K, V> _kv; // 存储键值对
RBTreeNode<K, V>* _left; // 左子树
RBTreeNode<K, V>* _right; // 右子树
RBTreeNode<K, V>* _parent; // 父节点
Colour _col; // 颜色属性
RBTreeNode(const pair<K, V>& kv)
: _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {}
};
每个节点不仅包含键值对 (_kv
),还包含指向左子树、右子树和父节点的指针。此外,节点的颜色 (_col
) 用于维护红黑树的平衡。
四、红黑树的插入操作
红黑树的插入操作需要遵循以下步骤:
- 按照二叉搜索树的规则插入新节点。这是插入操作的第一步,我们首先将新节点插入到树的适当位置。
- 将新节点的颜色设置为红色。新插入的节点默认是红色的,这有助于避免违反红黑树的黑色节点数平衡。
- 调整颜色和结构 。插入新节点后,可能会破坏红黑树的平衡。具体的修复操作包括:
- 情况1:变色操作。如果父节点和叔叔节点都是红色,则将父节点和叔叔节点都变为黑色,祖父节点变为红色,并继续往上处理。
- 情况2:旋转和变色。如果父节点是红色,且叔叔节点是黑色或不存在,则需要进行旋转操作(单旋或双旋),并相应地调整颜色。



我们来看一段插入代码的实现:
cpp
bool Insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_kv.first < kv.first) {
parent = cur;
cur = cur->_right;
} else if (cur->_kv.first > kv.first) {
parent = cur;
cur = cur->_left;
} else {
return false; // 键值重复
}
}
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
if (parent == grandfather->_left) {
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
} else {
if (cur == parent->_left) {
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
} else {
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
} else {
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
} else {
if (cur == parent->_right) {
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
} else {
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
这段代码展示了红黑树的插入操作,包括节点插入、颜色变更和旋转操作。关键的操作是通过循环和条件判断来确保红黑树的规则不被破坏。
五、红黑树的查找操作
红黑树的查找操作和普通的二叉搜索树是类似的,我们依然按照二叉搜索树的规则进行查找。由于红黑树的平衡性,查找操作的时间复杂度为 O(log N)。
cpp
Node* Find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_kv.first < key) {
cur = cur->_right;
} else if (cur->_kv.first > key) {
cur = cur->_left;
} else {
return cur;
}
}
return nullptr;
}
上述代码展示了查找操作的实现,我们根据节点的键值逐步向左或向右子树移动,直到找到目标节点或树为空。
六、红黑树的删除操作
删除操作在红黑树中比插入操作更为复杂。删除一个节点后,可能会破坏红黑树的平衡,特别是当删除的节点是黑色时,需要特别注意。因此,删除操作需要执行旋转和变色操作来恢复平衡。尽管删除操作复杂,但其时间复杂度同样是 O(log N)。
由于删除操作较为复杂,本文不深入讨论其实现。如果有兴趣,可以参考《算法导论》或《STL源码剖析》中的相关章节。
七、红黑树的验证
为了验证红黑树的正确性,我们可以检查它是否满足四条基本规则。可以通过前序遍历树的每一条路径,检查节点颜色和黑色节点数量是否符合要求。
cpp
bool Check(Node* root, int blackNum, const int refNum) {
if (root == nullptr) {
if (refNum != blackNum) {
cout << "存在黑色结点数量不相等的路径" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent->_col == RED) {
cout << root->_kv.first << "存在连续的红色结点" << endl;
return false;
}
if (root->_col == BLACK) {
blackNum++;
}
return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}
通过这个检查函数,我们能够验证树的结构是否符合红黑树的规则。
八、总结
红黑树是一种自平衡的二叉搜索树,通过简单的规则保证树的平衡性,确保了查找、插入和删除操作的时间复杂度始终为 O(log N)。虽然红黑树的插入、删除操作相对复杂,但它的高效性和稳定性使其成为许多应用中不可或缺的数据结构。希望通过本文的详细解析,大家能够对红黑树有更深入的了解。
!!预告,下一期详细讲红黑树的变换过程