C++模板实现红黑树:核心原理与完整代码解析
红黑树是一种自平衡二叉查找树,其核心在于通过节点颜色约束确保查询效率稳定在O(log n)。本文将深入解析红黑树的定义、五大核心性质,并详细拆解插入算法中的旋转与变色逻辑。提供基于C++模板的完整实现代码,包括结构体定义、左右单/双旋处理以及树的合法性校验方法,帮助开发者彻底掌握这一复杂数据结构。
红黑树的概念与性质
红黑树本质上是二叉搜索树的升级形态,它通过在每个节点上增加一个存储位来表示颜色(红色或黑色),从而对任意一条从根到叶子的路径进行着色限制。这种限制确保了没有一条路径会比其他路径长出两倍,使得整棵树接近平衡,但不同于AVL树的绝对高度平衡,红黑树追求的是相对平衡以牺牲少量查询极值性能来换取更好的增删性能。
红黑树必须严格满足以下五条性质:
- 每个节点只能是红色或黑色。
- 根节点必须是黑色。
- 如果一个节点是红色,则它的两个子节点必须是黑色(即不存在连续的两个红色节点)。
- 从任意节点到其每个叶子节点的简单路径上,均包含相同数目的黑色节点(即“黑高”相同)。
- 所有的叶子节点(空节点/NIL)都是黑色的。
性质原理解析:
设黑高为 $N$,最短路径即为全黑路径,长度约为 $N$。由于红色节点不能连续,最长路径则是红黑交替的路径,最大长度约为 $2N$。因此,红黑树保证了任何路径长度都不会超过最短路径的两倍。
红黑树的结构定义
在C++中,通常使用模板类来实现通用的红黑树。节点采用三叉链表结构(父、左、右指针),并记录键值对及颜色状态。
// 颜色枚举
enum Colour { RED, BLACK };
// 红黑树节点结构
template
struct RBTreeNode {
RBTreeNode(const pair& kv = pair(), Colour col = RED)
: _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(col) {}
RBTreeNode* _left; // 左孩子
RBTreeNode* _right; // 右孩子
RBTreeNode* _parent; // 父亲
pair _kv; // 键值对
Colour _col; // 节点颜色
};
// 红黑树类封装
template
class RBTree {
typedef RBTreeNode Node;
public:
RBTree() : _root(nullptr) {}
private:
Node* _root; // 根节点指针
}; 插入逻辑与维护机制
新插入节点的默认颜色设为红色。如果父节点为空(树为空),直接设为根节点并为黑色。否则,若父节点为黑色,插入后不破坏性质;若父节点为红色,则违反了性质3,必须进行修复调整。调整策略取决于叔叔节点(父亲兄弟节点)的存在及其颜色。
pairInsert(const pair & kv) { if (_root == nullptr) { _root = new Node(kv, BLACK); // 根节点强制黑色 return { _root, true }; } // 1. 普通BST查找插入位置 Node* cur = _root, * parent = nullptr; while (cur) { parent = cur; if (cur->_kv.first < kv.first) cur = cur->_right; else if (cur->_kv.first > kv.first) cur = cur->_left; else return { cur, false }; // 已存在 } cur = new Node(kv, RED); if (parent->_kv.first < kv.first) parent->_right = cur; else parent->_left = cur; cur->_parent = parent; // 2. 红黑树性质维护循环 while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; // 情况一:P在G的左边 if (parent == grandfather->_left) { Node* uncle = grandfather->_right; // 1.1 叔叔存在且为红色:变色处理 if (uncle && uncle->_col == RED) { parent->_col = uncle->_col = BLACK; grandfather->_col = RED; // 继续向上检查 cur = grandfather; parent = cur->_parent; } // 1.2 叔叔不存在或为黑色:旋转处理 else { // 直线情况:P在L,需右单旋 if (cur == parent->_left) { RotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED; } // 折线情况:P在R,需先左旋再右旋 else { RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } // 情况二:P在G的右边(对称逻辑) 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); grandfather->_col = RED; parent->_col = BLACK; } // 折线情况:双旋 else { RotateR(parent); RotateL(grandfather); parent->_col = grandfather->_col = RED; cur->_col = BLACK; } break; } } } _root->_col = BLACK; // 确保最终根为黑色 return { cur, true }; }
拆解讨论:插入变色的可视化逻辑
当新增节点位于祖父左侧(G-L-P),且父亲(P)和叔叔(U)均为红色时,只需将P、U变为黑色,G变为红色。这保持了G所在路径的黑高不变,只是将冲突向上层传递。
// Uncle为红时的变色逻辑
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}当叔叔为黑色或缺失时,需要根据当前节点(Cur)、父亲(P)、祖父(G)的排列形状决定旋转方式:
// 直线型:右单旋 (Cur -> Parent -> GrandFather)
if (parent->_left == cur) {
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
// 折线型:双旋 (Cur -> Right of Parent -> Left of GrandFather)
else {
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}旋转操作的实现
旋转是为了在不改变中序遍历结果的前提下调整树形结构。旋转时必须注意更新父子指针,特别是根节点的指针替换。
void RotateR(Node* parent) { // 右单旋
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR) subLR->_parent = parent;
subL->_right = parent;
Node* pp = parent->_parent;
parent->_parent = subL;
// 更新subL的父级连接
if (_root == parent) {
_root = subL;
subL->_parent = nullptr;
} else {
if (pp->_left == parent) pp->_left = subL;
else pp->_right = subL;
subL->_parent = pp;
}
}
void RotateL(Node* parent) { // 左单旋
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL) subRL->_parent = parent;
subR->_left = parent;
Node* pp = parent->_parent;
parent->_parent = subR;
if (_root == parent) {
_root = subR;
subR->_parent = nullptr;
} else {
if (pp->_left == parent) pp->_left = subR;
else pp->_right = subR;
subR->_parent = pp;
}
}红黑树合法性的验证
编写单元测试或调试工具时,通常需要校验红黑树的合法性。主要检查两点:无连续红色节点;所有根到空路径上的黑高一致。
bool CheckBalance() {
if (!_root) return true;
if (_root->_col != BLACK) return false; // 根必须黑
// 统计根到最左路径的黑高
int blackCount = 0;
Node* left = _root;
while (left) {
if (left->_col == BLACK) blackCount++;
left = left->_left;
}
return _CheckBalance(_root, blackCount, 0);
}
bool _CheckBalance(Node* root, int isBlackNum, int count) {
if (!root) {
if (count != isBlackNum) {
printf("Path Black Count Mismatch\n");
return false;
}
return true;
}
// 检查连续红色
if (root->_col == RED && root->_parent->_col == RED) {
printf("Continuous Red Nodes Found\n");
return false;
}
// 累加黑高
if (root->_col == BLACK) count++;
// 递归检查左右子树
return _CheckBalance(root->_left, isBlackNum, count) &&
_CheckBalance(root->_right, isBlackNum, count);
}应用场景与总结
红黑树因其实现相对简单且在频繁增删场景下性能优异,被广泛应用于工程领域。例如 C++ STL 中的 map/set/multimap/multiset,Linux 内核的事件调度、进程调度等,以及 Java 集合框架中的 TreeMap 和 TreeSet。
以上就是C++实现红黑树的详细内容,更多关于C++红黑树数据结构的学习资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













