跳到主要内容
C++ 红黑树原理与手写实现详解 | 极客日志
C++ 算法
C++ 红黑树原理与手写实现详解 红黑树作为自平衡二叉搜索树,利用颜色标记与局部旋转维持近似平衡,确保查找、插入、删除复杂度为 O(logN)。文章深入剖析红黑树五大性质,推导其高度约束原理。重点讲解插入时的四种调整策略:叔叔节点红色时变色上移,黑色时根据 LL、RR、LR、RL 形态进行单旋或双旋配合变色。提供完整 C++ 模板实现,涵盖节点结构、插入逻辑、旋转函数及平衡性验证代码。对比 AVL 树测试表明,红黑树在频繁增删场景下旋转次数更少,工程实用性更强,是 C++ STL map 与 set 容器的底层数据结构基础。
橘子海 发布于 2026/3/21 更新于 2026/5/1 6 浏览前言
在上一篇文章中,我们从零实现了 AVL 树,深入理解了二叉搜索树的平衡思想。然而,AVL 树虽然严格平衡,但插入和删除操作频繁时,频繁的旋转会带来较大的性能损耗。
为了在'平衡性'和'性能'之间取得更好的折中,红黑树(Red-Black Tree)应运而生。它通过为每个结点引入'颜色属性',并对节点间的颜色分布施加约束,使得树在近似平衡的状态下仍能保持高效的查找、插入与删除性能。
红黑树并不追求绝对平衡,而是通过「颜色规则 + 局部旋转」实现'动态平衡'。
这种思想也正是 C++ STL 容器 map / set 的底层核心机制。
本文将从最基本的概念出发,逐步剖析红黑树的性质、插入规则、旋转逻辑,并最终完成一个完整可运行的红黑树实现。我们还将通过与 AVL 树的性能对比,深入理解红黑树在实际工程场景中的优势与应用价值。
1. 什么是红黑树
概念与定义
红黑树是一种自平衡的二叉搜索树,由德国计算机科学家 Rudolf Bayer 在 1972 年发明,当时被称为对称二叉 B 树,后来被 Leo J. Guibas 和 Robert Sedgewick 改进并命名为红黑树。
它在很多编程语言的库和实际应用场景中都有广泛使用:
C++ 的 STL 库中的 map 和 set 底层实现
Java 的 TreeMap 等库的实现
它通过额外的颜色标记和旋转操作来维持树的平衡,确保最坏情况下的基本操作(插入、删除、查找)时间复杂度为 O(logN)。
它在每个节点上增加了一个存储位来表示节点的颜色,这个颜色可以是红色或黑色。通过对从根节点到任意叶子节点路径上的节点颜色进行约束来保持树的左右两边的相对平衡,红黑树确保没有一条路径会比其他路径长出 2 倍,即 2 * 最短路径 >= 最长路径,因而它是一种近似平衡的二叉搜索树。
2. 红黑树的性质
红黑树的性质如下:
每个结点不是红色就是黑色
根节点是黑色的
如果一个节点是红色的,则它的两个孩子结点必须是黑色的
对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
每个叶子结点都是黑色的 (此处的叶子结点指的是空结点)
红黑树的性质解读
性质总结:
左根右:由于它是二叉搜索树,所以左子树的值 < 根的值 < 右子树的值;
根叶黑:根节点和叶子节点 (NIL) 都是黑色的;
不红红:一条路径上不会出现两个连续的红色节点;
黑路同:任意两条路径上的黑色节点个数相同。
因此,红黑树的性质可以简记为:左根右,根叶黑,不红红,黑路同。
树的路径再认识
计算路径时,是从根节点到空结点(NIL),为一条路径。如果树路径的计算不包含空节点,那么某些看似平衡的树其实违反了性质 4。
3. 红黑树如何确保最长路径不超过最短路径的 2 倍?
红黑树路径规则推导:
最短路径(全黑路径) :
由红黑树性质 4 可知,极端场景下,最短路径就是全由黑色结点组成的路径。我们把这条最短路径的长度记为 bh(black height,黑色高度)。
最长路径(黑红间隔路径) :
结合规则 2(根结点是黑色)和规则 3(红色结点的子结点必为黑色,路径中不会出现连续红色结点)。极端场景下,最长路径会呈现一黑一红交替的结构,因此最长路径长度为 2*bh。
路径长度范围 :
实际红黑树中,'全黑最短路径'和'黑红交替最长路径'不一定同时存在。但通过 4 条规则可推导:任意从根到 NIL 结点的路径长度 h,都满足 bh ≤ h ≤ 2*bh,这保证了红黑树的近似平衡特性。
4. 红黑树的实现
整体架构设计
结点颜色的枚举类 enum Colour { Red, Black };
红黑树的结点定义 红黑树结点为模版实现。这里我们使用 std::pair<K, V> 来存储我们的键值对数据。结点采用 struct 设计,默认权限为 public,方便下文的 RBTree 类访问成员。
template <class K , class V >
struct RBTreeNode {
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode (const pair<K, V>& kv) : _left(nullptr ), _right(nullptr ), _parent(nullptr ), _kv(kv), _col(Red) {}
};
思考一下:在节点的定义中,为什么要将节点的默认颜色给成红色的?因为插入红色节点更容易调整,避免破坏黑高一致性。
红黑树设计 template <class K , class V >
class RBTree {
typedef RBTreeNode<K, V> Node;
RBTreeNode<K, V>* _root = nullptr ;
public :
private :
};
红黑树的插入实现
1. 空树的插入 当我们对一棵空的红黑树进行插入时,直接插入并把其颜色设置为黑色即可。
2. 新插入节点的父亲为黑色 首先我们需要明确一点,在红黑树非空的情况下,我们插入一个节点的颜色必定是红色。
如果我们把插入节点的颜色设置为黑色的话,那么此时这棵红黑树就必然会违反黑路同的性质,插入黑色结点会对其他路径造成影响,之后想要把每条路上的黑色节点数量调整为一样多的话就会非常麻烦。所以从这个角度来看,树非空的情况下我们插入的节点都设置为红色。
因此,插入一个红色节点,如果它的父亲是黑色,那么将不会违反红黑树的任何一条性质,插入结束。
3. 新插入节点的父亲为红色 我们插入的节点为红色,此时如果它的父亲也为红色,那么就会违反不红红的性质。那么此时让父亲变为黑色,但这也意味着父亲这条路径上多了一个黑色节点,违反了黑路同,我们需要调节黑色节点的数量。
此时可以让父亲的父亲 (grandfather) 变为红色 (由于变色前父亲为红,所以爷爷变色之前必然为黑),再让叔叔节点变为黑色,这样一来,我们就可以同时满足不红红和黑路同两个性质了。
但是,此时由于爷爷变红,可能又会导致出现两个红色节点相邻的情况,因为爷爷的父亲可能为红色。此时我们只需要把爷爷当作新插入的节点,重复上面的操作即可。
一直重复这样的操作,直到不违反红黑树的性质或者到根为止。注意 grandfather 到根的时候需要把根变为黑色。
(1)叔叔存在且为红色:变色 + 继续向上处理 这种情况比较简单,主要是变色操作。如果叔叔节点存在且为红色,我们将父亲和叔叔设为黑色,祖父设为红色,然后以祖父为新节点继续向上检查。
(2)叔叔不存在或叔叔为黑色:旋转 + 变色 当叔叔节点不存在或为黑色时,我们需要通过旋转来调整结构。
LL 型:右单旋 + 变色
RR 型:左单旋 + 变色
LR 型:左右双旋 + 变色
RL 型:右左双旋 + 变色
如果是 RL 型,那么需要将以爷爷节点为根的子树进行右左双旋操作,旋转之后将 cur 和爷爷节点进行变色,插入结束。这种情况与 LR 型完全对称。
4. 旋转操作 这里的旋转操作和 AVL 树的旋转操作一致,只需把 AVL 树中的更新平衡因子的步骤去掉。
左单旋
void RotateL (Node* parent) {
_rotateCount++;
if (parent == nullptr || parent->_right == nullptr )
return ;
Node* curNode = parent->_right;
Node* curLeft = curNode->_left;
parent->_right = curLeft;
if (curLeft)
curLeft->_parent = parent;
if (parent == _root) {
_root = curNode;
curNode->_parent = nullptr ;
parent->_parent = curNode;
curNode->_left = parent;
} else {
Node* ppNode = parent->_parent;
if (parent == ppNode->_left)
ppNode->_left = curNode;
else
ppNode->_right = curNode;
curNode->_parent = ppNode;
parent->_parent = curNode;
curNode->_left = parent;
}
}
右单旋
void RotateR (Node* parent) {
_rotateCount++;
if (parent == nullptr || parent->_left == nullptr )
return ;
Node* curNode = parent->_left;
Node* curRight = curNode->_right;
parent->_left = curRight;
if (curRight)
curRight->_parent = parent;
if (parent == _root) {
_root = curNode;
curNode->_parent = nullptr ;
curNode->_right = parent;
parent->_parent = curNode;
} else {
Node* ppNode = parent->_parent;
if (parent == ppNode->_left)
ppNode->_left = curNode;
else
ppNode->_right = curNode;
curNode->_parent = ppNode;
curNode->_right = parent;
parent->_parent = curNode;
}
}
5. 插入的完整代码 bool insert (const pair<K, V>& kv) {
if (_root == nullptr ) {
_root = new Node (kv);
_root->_col = Black;
return true ;
}
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode) {
if (kv.first < curNode->_kv.first) {
parent = curNode;
curNode = curNode->_left;
} else if (kv.first > curNode->_kv.first) {
parent = curNode;
curNode = curNode->_right;
} else
return false ;
}
curNode = new Node (kv);
if (curNode->_kv.first < parent->_kv.first)
parent->_left = curNode;
else
parent->_right = curNode;
curNode->_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;
curNode = grandFather;
parent = curNode->_parent;
}
else {
if (curNode == parent->_left) {
RotateR (grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
else {
RotateL (parent);
RotateR (grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break ;
}
}
else {
Node* uncle = grandFather->_left;
if (uncle && uncle->_col == Red) {
parent->_col = uncle->_col = Black;
grandFather->_col = Red;
curNode = grandFather;
parent = curNode->_parent;
}
else {
if (curNode == parent->_right) {
RotateL (grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
else {
RotateR (parent);
RotateL (grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break ;
}
}
}
_root->_col = Black;
return true ;
}
5. 验证红黑树
求树的高度
先分别求树的左右子树高度
最终返回左右子树中 高度更大的高度 + 1
int Height () {
return _Height(_root);
}
int _Height(Node* root = _root) {
if (root == nullptr )
return 0 ;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1 ;
}
判断树是否是红黑树
判断是否平衡 通过判断黑色节点的数量来判断黑色节点是否满足红黑树性质。
bool isBalance () {
return _isBalance(_root);
}
bool _isBalance(Node* root = _root) {
if (root == nullptr )
return true ;
if (root->_col != Black)
return false ;
int benchmark = 0 ;
Node* cur = _root;
while (cur) {
if (cur->_col == Black)
++benchmark;
cur = cur->_left;
}
return CheckColour (root, 0 , benchmark);
}
检查根节点的颜色是否为黑色
计算树中最左路径中,黑色结点的数量作为基准值,传给 CheckColour 函数,与其他路径的黑色节点数量进行比对
检查红黑树的颜色 bool CheckColour (Node* root, int blacknum, int benchmark) {
if (root == nullptr ) {
if (blacknum != benchmark)
return false ;
return true ;
}
if (root->_col == Black)
++blacknum;
if (root->_col == Red && root->_parent && root->_parent->_col == Red) {
cout << root->_kv.first << "出现连续红色节点" << endl;
return false ;
}
return CheckColour (root->_left, blacknum, benchmark) && CheckColour (root->_right, blacknum, benchmark);
}
递归检查,检查树中是否有连续的红色结点:若当前节点和它的父节点都是红色,则出现了连续的红色节点,违反红黑树性质④,输出提示并返回 false。
递归检查,检查其他路径中黑色结点的数量与基准值是否相同。如果当前节点是黑色,就增加路径上的黑节点计数。当遍历到 nullptr 时,说明到达叶子。此时如果当前路径上的黑节点数与标准值不同,则不满足红黑树性质⑤,返回 false。
对左右子树分别检查,只有两边都满足条件,整棵树才合法。注意:blacknum 是值传递,在每条递归路径中互不影响。
blacknum 使用'传值': 形参传值会拷贝一份当前的 blacknum,在递归调用中独立变化。
也就是说:
每条递归路径拥有自己独立的黑节点计数,不会因为返回后互相干扰;
非常适合这种'每条路径独立计算'的场景。
6. 红黑树与 AVL 树性能测试对比 红黑树和 AVL 树都是高效的平衡二叉树,增删改查的时间复杂度都是 O(log₂N)。红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的 2 倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比 AVL 树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
测试代码 #include "Red_Black_Tree.h"
#include "AVL_Tree.h"
#include <vector>
int main () {
const int N = 10000000 ;
vector<int > v;
v.reserve (N);
srand (time (0 ));
for (size_t i = 0 ; i < N; i++) {
v.push_back (rand () + i * i + 2 );
}
RBTree<int , int > rbt;
for (auto e : v) {
rbt.insert (make_pair (e, e));
}
cout << "红黑树是否平衡:" << rbt.isBalance () << endl;
cout << "红黑树的高度:" << rbt.Height () << endl;
cout << "红黑树的旋转次数:" << rbt._rotateCount << endl;
cout << endl << endl;
AVLTree<int , int > avlt;
for (auto e : v) {
avlt.insert (make_pair (e, e));
}
cout << "AVL 树是否平衡:" << avlt.isBalance () << endl;
cout << "AVL 树的高度:" << avlt.height () << endl;
cout << "AVL 树的旋转次数:" << avlt._rotateCount << endl;
return 0 ;
}
插入有序数据的结果
插入随机无序数据的结果
7. 完整代码 #pragma once
#include <iostream>
using namespace std;
enum Colour { Red, Black };
template <class K , class V >
struct RBTreeNode {
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode (const pair<K, V>& kv) : _left(nullptr ), _right(nullptr ), _parent(nullptr ), _kv(kv), _col(Red) {}
};
template <class K , class V >
class RBTree {
public :
int _rotateCount = 0 ;
private :
typedef RBTreeNode<K, V> Node;
RBTreeNode<K, V>* _root = nullptr ;
public :
bool insert (const pair<K, V>& kv) {
if (_root == nullptr ) {
_root = new Node (kv);
_root->_col = Black;
return true ;
}
Node* parent = nullptr ;
Node* curNode = _root;
while (curNode) {
if (kv.first < curNode->_kv.first) {
parent = curNode;
curNode = curNode->_left;
} else if (kv.first > curNode->_kv.first) {
parent = curNode;
curNode = curNode->_right;
} else
return false ;
}
curNode = new Node (kv);
if (curNode->_kv.first < parent->_kv.first)
parent->_left = curNode;
else
parent->_right = curNode;
curNode->_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;
curNode = grandFather;
parent = curNode->_parent;
}
else {
if (curNode == parent->_left) {
RotateR (grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
else {
RotateL (parent);
RotateR (grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break ;
}
}
else {
Node* uncle = grandFather->_left;
if (uncle && uncle->_col == Red) {
parent->_col = uncle->_col = Black;
grandFather->_col = Red;
curNode = grandFather;
parent = curNode->_parent;
}
else {
if (curNode == parent->_right) {
RotateL (grandFather);
parent->_col = Black;
grandFather->_col = Red;
}
else {
RotateR (parent);
RotateL (grandFather);
curNode->_col = Black;
grandFather->_col = Red;
}
break ;
}
}
}
_root->_col = Black;
return true ;
}
bool CheckColour (Node* root, int blacknum, int benchmark) {
if (root == nullptr ) {
if (blacknum != benchmark)
return false ;
return true ;
}
if (root->_col == Black)
++blacknum;
if (root->_col == Red && root->_parent && root->_parent->_col == Red) {
cout << root->_kv.first << "出现连续红色节点" << endl;
return false ;
}
return CheckColour (root->_left, blacknum, benchmark) && CheckColour (root->_right, blacknum, benchmark);
}
bool isBalance () {
return _isBalance(_root);
}
bool _isBalance(Node* root = _root) {
if (root == nullptr )
return true ;
if (root->_col != Black)
return false ;
int benchmark = 0 ;
Node* cur = _root;
while (cur) {
if (cur->_col == Black)
++benchmark;
cur = cur->_left;
}
return CheckColour (root, 0 , benchmark);
}
int Height () {
return _Height(_root);
}
int _Height(Node* root = _root) {
if (root == nullptr )
return 0 ;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1 ;
}
private :
void RotateL (Node* parent) {
_rotateCount++;
if (parent == nullptr || parent->_right == nullptr )
return ;
Node* curNode = parent->_right;
Node* curLeft = curNode->_left;
parent->_right = curLeft;
if (curLeft)
curLeft->_parent = parent;
if (parent == _root) {
_root = curNode;
curNode->_parent = nullptr ;
parent->_parent = curNode;
curNode->_left = parent;
} else {
Node* ppNode = parent->_parent;
if (parent == ppNode->_left)
ppNode->_left = curNode;
else
ppNode->_right = curNode;
curNode->_parent = ppNode;
parent->_parent = curNode;
curNode->_left = parent;
}
}
void RotateR (Node* parent) {
_rotateCount++;
if (parent == nullptr || parent->_left == nullptr )
return ;
Node* curNode = parent->_left;
Node* curRight = curNode->_right;
parent->_left = curRight;
if (curRight)
curRight->_parent = parent;
if (parent == _root) {
_root = curNode;
curNode->_parent = nullptr ;
curNode->_right = parent;
parent->_parent = curNode;
} else {
Node* ppNode = parent->_parent;
if (parent == ppNode->_left)
ppNode->_left = curNode;
else
ppNode->_right = curNode;
curNode->_parent = ppNode;
curNode->_right = parent;
parent->_parent = curNode;
}
}
};
int main () {
const int N = 10000000 ;
vector<int > v;
v.reserve (N);
srand (time (0 ));
for (size_t i = 0 ; i < N; i++) {
v.push_back (rand () + i);
}
RBTree<int , int > rbt;
for (auto e : v) {
rbt.insert (make_pair (e, e));
}
cout << "红黑树是否平衡:" << rbt.isBalance () << endl;
cout << "红黑树的高度:" << rbt.Height () << endl;
cout << "红黑树的旋转次数:" << rbt._rotateCount << endl;
cout << endl << endl;
AVLTree<int , int > avlt;
for (auto e : v) {
avlt.insert (make_pair (e, e));
}
cout << "AVL 树是否平衡:" << avlt.isBalance () << endl;
cout << "AVL 树的高度:" << avlt.height () << endl;
cout << "AVL 树的旋转次数:" << avlt._rotateCount << endl;
return 0 ;
}
结语 红黑树作为一种'近似平衡'的二叉搜索树,通过颜色标记与旋转调整,在插入与删除操作频繁的场景下有效避免了 AVL 树的高频旋转问题。
它在时间复杂度上与 AVL 树相同,都是 O(logN),但实际应用中更具工程价值,因此成为了 STL map、set、multimap、multiset 等容器的底层数据结构。
本文自底向上实现了红黑树的插入、变色与旋转机制,并通过与 AVL 树的对比,揭示了红黑树在平衡效率与维护开销上的折中之美。
✅ 红黑树的核心思想:不追求完美平衡,而是保证最长路径不超过最短路径的 2 倍;通过简单的局部旋转与变色操作实现全局近似平衡;用颜色规则取代复杂的高度平衡因子,使得结构更简洁、效率更高。
红黑树的实现也标志着我们正式跨入了 C++ STL 底层机制探索的第二阶段。下一步,我们将基于该结构实现 map、set 等容器,深入理解迭代器、模板参数与仿函数在标准库中的实际应用。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online