跳到主要内容C++红黑树原理与实现 | 极客日志C++算法
C++红黑树原理与实现
综述由AI生成C++ 红黑树的概念、五条核心规则及其与 AVL 树的性能对比。详细阐述了红黑树的节点结构、插入逻辑(包括初始颜色设置、颜色调整策略及旋转操作),并提供了完整的验证平衡性的代码实现。通过左单旋、右单旋及变色操作维护红黑树性质,确保查找效率稳定在 O(log n)。
魔尊18 浏览 
一、红黑树的概念
红黑树是一棵二叉搜索树,它的每个结点增加一个存储位来表示结点的颜色,可以是红色或者黑色,通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出 2 倍,因而是接近平衡的。
二、红黑树的规则

- 每个
结点 不是 红色 就是 黑色
根 节点是 黑 色的
- 如果一个节点是
红色 的,则它的两个 孩子 结点是 黑色 的
- 对于每个结点,从该结点到其所有后代叶结点的简单
路径 上,均包含 相同数目 的 黑色 结点
- 每个
叶子 结点都是 黑色 的 (此处的叶子结点指的是 空 结点):
规则解释:
- 什么是路径?对于上图来说,一共有 11 条路径,根到空节点算一条路径。
- 红色结点的孩子必须是黑色结点,也就是说任何路径没有连续的红色节点。
- 每条路径上黑色节点的数量相等
- 对于任意一个结点,从该结点到其所有 NULL 结点的简单路径上,均包含相同数量的黑色结点。说明:《算法导论》等书籍上补充了一条每个叶子结点 (NIL) 都是黑色的规则。他这里所指的叶子结点不是传统的意义上的叶子结点,而是我们说的空结点,有些书籍上也把 NIL 叫做外部结点。NIL 是为了方便准确的标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了 NIL 结点,所以我们知道一下这个概念即可。
- 为什么说只要满足了上述所有规则,就能保证红黑树其最长路径中节点个数不会超过最短路径节点个数的两倍?
只要满足了上述规则,最短的路径将会是全是黑色,最长的路径将是一红一黑红黑相间:
由规则 4 可知,从根到 NULL 结点的每条路径都有相同数量的黑色结点,所以极端场景下,最短路径就就是全是黑色结点的路径,假设最短路径长度为 bh(black height)。
由规则 2 和规则 3 可知,任意一条路径不会有连续的红色结点,所以极端场景下,最长的路径就是黑一红间隔组成,那么最长路径的长度为 2*bh。
综合红黑树的 4 点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在的。假设任意一条从根到 NULL 结点路径的长度为 x,那么 bh<=h<=2*bh。

三、红黑树与 AVL 树性能对比
性能差异总结:
- AVL 树:
高度较低,查找效率更高。插入和删除操作由于需要频繁调整平衡,开销较大。红黑树:
- 平衡条件较宽松(最长路径不超过最短路径的 2 倍)。
- 高度比 AVL 树稍高,查找效率略低。
- 插入和删除操作调整较少,性能更优。
| 树种类 | 平衡条件 | 高度(1000 个值) | 高度(10 亿个值) | 查找效率 | 插入/删除效率 |
|---|
| AVL 树 | 高度差不超过 1 | (log N = 10) | (log N = 30) | 更高 | 较低 |
| 红黑树 | 最长路径 ≤ 最短路径的 2 倍 | (2log N = 20) | (2log N = 60) | 略低 | 更高 |
四、红黑树框架的搭建
#pragma once
#include <iostream>
using namespace std;
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){}
};
template<class K, class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
五、红黑树的插入逻辑
1. 大体框架
首先,插入逻辑的答题思路是和前面我们所学的 AVL 树的插入主体框架是大体相同的,代码如下:
bool Insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) {
if (kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
} else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
} else {
return false;
}
}
cur = new Node(kv);
if (cur->_kv.first < parent->_kv.first) {
parent->_left = cur;
} else {
parent->_right = cur;
}
cur->_parent = parent;
return true;
}
2. 初始颜色的设置
首先迎来我们第一个问题,新插入结点初始的颜色要设置成什么呢?
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
如果我们选择插入黑色,那么就会违背规则 4,那么画出的图是这样的:
就会造成所有路径黑色结点出现问题,就要更改所有路径的风险,因此我们不选择这种方案。
如果选择插入红色结点,就会违背规则 3,画出的图是这样的:
如果插入到红色节点后,则只牵扯到当前路径,很容易进行调整。因此我们新插入的结点默认为 红色!!!
cur = new Node(kv);
cur->_col = RED;
3. 颜色的调整
1)通过具体的情况对颜色调整进行框架认识
情况一:uncle 存在,且为红色:
解决方式:parent 与 uncle 变黑,grandfather 变红,继续向上调整。
情况二:uncle 不存在
解决方式:旋转 + 变色
情况三:uncle 存在且为黑色
解决方式:旋转 + 变色
2)通过抽象图对颜色调整加深理解
情况一:uncle 存在且 uncle 为红色
while (parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
if (parent == grandfather->_left) {
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED) {
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
} else
{
}
}
_root->_col = BLACK;
}
情况二:uncle 不存在或 uncle 存在且 uncle 为黑色
因为这里牵扯到旋转,所以我们需要知道旋转的逻辑,这个之前做过专题讲解过,不会的小伙伴可以参考相关资料。
下面我们在这里再放以下旋转的代码~
唯一的区别就是,这里不需要平衡因子。
void RotateL(Node* parent) {
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = curleft;
if (curleft)
{
curleft->_parent = parent;
}
cur->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr) {
_root = cur;
cur->_parent = nullptr;
} else {
if (ppnode->_left == parent) {
ppnode->_left = cur;
} else {
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
void RotateR(Node* parent) {
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright) {
curright->_parent = parent;
}
cur->_right = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr) {
cur->_parent = nullptr;
_root = cur;
} else {
if (ppnode->_left == parent) {
ppnode->_left = cur;
} else {
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
} 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;
}
六、红黑树是否构建正确的检查
以下的这段代码的核心目标是检查红黑树是否构建正确,主要基于红黑树的规则进行验证。代码的设计分为两个关键部分:计算基准值和递归验证规则。
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) {
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);
}
- 基准值的计算
在红黑树中,所有从根节点到叶节点的路径上,必须经过相同数量的黑色节点。为此,代码首先沿着树的左侧路径计算路径上黑色节点的数量,作为基准值
benchmark。这是验证红黑树平衡性的关键步骤。
- 递归验证红黑树规则
接下来通过递归函数
CheckColour,对红黑树的所有节点进行逐一验证,确保其符合以下规则:
- 每个节点是红色或黑色。
- 红色节点的子节点必须是黑色(即不能出现连续的红色节点)。
- 从任一节点到其所有后代的每条路径包含相同数量的黑色节点。
- 首先调用
IsBalance(),对整棵树进行平衡性检查。
- 先判断根节点是否为黑色,这是红黑树的基本要求。
- 沿着左侧路径,计算从根节点到最左叶节点的黑色节点总数,作为基准值。
- 然后进入递归检查函数
CheckColour():
- 如果当前节点为空(即到达叶节点),检查该路径累计的黑色节点数量是否与基准值一致。
- 对每个非空节点,检查其颜色是否满足规则:红色节点不能有红色父节点。
- 累计当前路径上的黑色节点数,并递归检查左右子树。
- 最终通过递归结果判断整棵树是否满足红黑树性质。
- 如果发现不符合的地方(如连续红色节点或黑色节点数不一致),会提前返回
false 并结束验证。
我们运行的时候给 10000000 个随机值,看看会不会调用成功。
#include "RBTree_1.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(i);
}
RBTree<int, int> rbt;
for (auto e : v) {
rbt.Insert(make_pair(e, e));
}
cout << rbt.IsBalance() << endl;
return 0;
}
七、红黑树总体代码总结
#pragma once
#include <iostream>
using namespace std;
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){}
};
template<class K, class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv) {
if (_root == nullptr) {
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) {
if (kv.first < cur->_kv.first) {
parent = cur;
cur = cur->_left;
} else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
} else {
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
if (cur->_kv.first < parent->_kv.first) {
parent->_left = cur;
} else {
parent->_right = 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 = BLACK;
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 = BLACK;
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 RotateL(Node* parent) {
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = curleft;
if (curleft)
{
curleft->_parent = parent;
}
cur->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr) {
_root = cur;
cur->_parent = nullptr;
} else {
if (ppnode->_left == parent) {
ppnode->_left = cur;
} else {
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
void RotateR(Node* parent) {
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright) {
curright->_parent = parent;
}
cur->_right = parent;
Node* ppnode = parent->_parent;
parent->_parent = cur;
if (ppnode == nullptr) {
cur->_parent = nullptr;
_root = cur;
} else {
if (ppnode->_left == parent) {
ppnode->_left = cur;
} else {
ppnode->_right = cur;
}
cur->_parent = ppnode;
}
}
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) {
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);
}
private:
Node* _root = nullptr;
};
相关免费在线工具
- 加密/解密文本
使用加密算法(如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