红黑树详解
基本概念
红黑树是一种自平衡的二叉搜索树。它在每个节点上增加了一个存储位来表示颜色(红色或黑色),通过对路径上颜色的约束,确保从根到叶子的最长路径不会超过最短路径的两倍,从而近似保持平衡。
核心规则
要成为一棵合法的红黑树,必须满足以下五条性质:
- 节点颜色:每个节点要么是红色,要么是黑色。
- 根节点:根节点必须是黑色。
- 红色限制:如果一个节点是红色,则它的两个子节点必须是黑色。这意味着任意一条路径上不能出现连续的红色节点。
- 黑色高度:对于任意一个节点,从该节点到其所有叶子节点(NIL 节点)的简单路径上,均包含相同数量的黑色节点。
- 叶子节点:所有的叶子节点(NIL)都是黑色的。
注意:数路径时要数到空结点(NIL)。这保证了树的深度控制在 O(logN) 级别。
插入调整策略
插入新节点时,我们默认将其染为红色,然后按照二叉搜索树的规则找到位置。如果破坏了红黑树的性质,就需要通过变色和旋转来修复。主要涉及三种情况:
情况一:叔叔节点存在且为红色
当父节点和当前节点都是红色,而叔叔节点也是红色时,说明祖父节点必然是黑色。此时只需将父节点和叔叔节点变黑,祖父节点变红,然后将祖父节点视为新的当前节点继续向上检查。这种情况只变色不旋转。
情况二:叔叔节点不存在或为黑色(单旋 + 变色)
当父节点和当前节点同侧(例如都是左孩子),且叔叔节点不存在或为黑色时,需要对祖父节点进行右旋,并交换颜色。这通常发生在新增节点导致不平衡时。
情况三:叔叔节点不存在或为黑色(双旋 + 变色)
当父节点和当前节点异侧(例如父是左,当前是右)时,需要先对父节点进行左旋,转化为情况二,然后再对祖父节点进行右旋。双旋操作能有效处理这种'之'字形的结构。
C++ 代码实现
下面是一个基于模板的红黑树实现示例。为了便于理解,我将核心逻辑整理成了清晰的类结构。在实际工程中,通常会配合 KeyOfT 函数对象来提取比较键值。
#include <iostream>
#include <vector>
using namespace std;
enum Colour { RED, BLACK };
template<class T>
struct RBTreeNode {
T _data;
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
RBTreeNode(const T& data)
:_data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {}
};
template<class K, class T, class KeyOfT>
class RBTree {
typedef RBTreeNode<T> Node;
public:
bool Insert(const T& data) {
if (_root == nullptr) {
_root = new Node(data);
_root->_col = BLACK;
return true;
}
KeyOfT kot;
Node* parent = nullptr;
Node* cur = _root;
// 1. 查找插入位置
while (cur) {
if (kot(cur->_data) < kot(data)) {
parent = cur;
cur = cur->_right;
} else if (kot(cur->_data) > kot(data)) {
parent = cur;
cur = cur->_left;
} else {
return false; // 已存在
}
}
// 2. 插入新节点(默认为红色)
cur = new Node(data);
cur->_col = RED;
if (kot(parent->_data) < kot(data)) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
// 3. 修复红黑树性质
while (parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
if (parent == grandfather->_left) {
Node* uncle = grandfather->_right;
// 情况 1: 叔叔存在且为红
if (uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
} else {
// 情况 2/3: 叔叔不存在或为黑
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;
}
void InOrder() {
_InOrder(_root);
cout << endl;
}
private:
int _Size(Node* root) {
return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
}
int _Height(Node* root) {
if (root == nullptr) return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
void _InOrder(Node* root) {
if (root == nullptr) return;
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
void RotateR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) subLR->_parent = parent;
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root) {
_root = subL;
subL->_parent = nullptr;
} else {
if (ppnode->_left == parent) ppnode->_left = subL;
else ppnode->_right = subL;
subL->_parent = ppnode;
}
}
void RotateL(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parent == _root) {
_root = subR;
subR->_parent = nullptr;
} else {
if (ppnode->_left == parent) ppnode->_left = subR;
else ppnode->_right = subR;
subR->_parent = ppnode;
}
}
private:
Node* _root = nullptr;
};
总结
红黑树通过严格的颜色约束和旋转机制,在插入、删除和查找操作上都能保证 O(logN) 的时间复杂度。相比于 AVL 树,它减少了旋转次数,更适合频繁插入的场景。理解其插入时的三种调整情况,是掌握这一数据结构的关键。

