前言:
- 在吃透AVL树 依靠高度差维护严格平衡的原理后,我们不难发现它的短板 :严苛的平衡判定会带来频繁的旋转操作,拉高了插入、删除场景的性能开销。而红黑树 作为工程界更常用的平衡二叉树方案,并没有沿用 AVL 树的高度管控思路,而是借助结点染色规则实现宽松的近似平衡。
- 本篇就基于已掌握的 AVL 树知识体系,一步步拆解红黑树的设计思想、约束规则与底层运行逻辑。
一、红黑树的概念
红黑树是一棵二叉搜索树,它的每个结点增加一个存储位来表示结点的颜色,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的。
(1)、红黑树的规则
- 规则一:每个结点不是红色就是黑色
- 规则二:根结点是黑色的
- 规则三:如果一个结点是红色的,则它的两个孩子结点必须是黑色的,也就是说任意一条路径不会有连续的红色结点。
- 规则四:对于任意一个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色结点




《算法导论》等书籍上补充了一条每个叶子结点 (NIL) 都是黑色的规则。这里所指的叶子结点不是传统的意义上的叶子结点,而是空结点,部分书籍中将 NIL 称作外部结点。NIL 用于精准标识所有路径,《算法导论》在讲解具体实现时会忽略 NIL 结点,了解该概念即可。

(2)、红黑树最长路径≤最短路径2倍的原理
-
最短路径推导
依据规则4,根到所有NULL结点的黑色结点数量一致。最短路径为全部由黑色结点构成,路径长度记为
bh(黑高)。 -
最长路径推导
依据规则2、规则3,树中不存在连续红色结点。极端最长路径为黑、红结点交替排布,路径长度最大值为
2*bh。 -
路径长度区间结论
全黑最短路径、红黑交替最长路径仅为理论极值,并非所有红黑树都会出现。设根到NULL的任意路径高度为hhh,满足:
bh≤h≤2bh\boldsymbol{bh \le h \le 2bh}bh≤h≤2bh
(3)、红黑树的效率
1、时间复杂度推导
设:NNN = 红黑树结点总数,hhh = 最短路径长度(黑高)
关系式:2h−1≤N<22h−12^h -1 \le N < 2^{2h}-12h−1≤N<22h−1
可推导出:h≈logNh \approx \log Nh≈logN
最坏情况下操作需要遍历最长路径 2logN2\log N2logN,增删查改时间复杂度依旧为 O(logN)O(\log N)O(logN)
2、红黑树与AVL树对比
平衡实现方式:
- AVL树:依靠结点高度差,直观、严格管控平衡
- 红黑树:依靠四条颜色规则约束,间接达成近似平衡
运行开销:
- 二者时间复杂度处于同一层级;红黑树平衡约束更宽松,插入结点时所需旋转次数更少,修改性能更优。
二、红黑树的实现
(1)、红黑树的结构
在AVL树结构的基础上增加了颜色枚举 Colour 和结点新增成员:Colour _col,去掉了平衡因子。
- 颜色枚举定义了 RED、BLACK 两种颜色,用来依靠颜色规则维护平衡,AVL 依靠平衡因子,没有颜色字段。
- 每个红黑树节点自带颜色属性,是红黑树维持弱平衡的核心标识,AVL 不存在该成员。
cpp
// 枚举值表示颜色
enum Colour
{
RED,
BLACK
};
// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
// 这里更新控制平衡也要加入parent指针
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)
{}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
private:
Node* _root = nullptr;
};
(2)、红黑树的插入
红黑树插入整体流程
节点代号定义:
c(cur):最新插入的当前节点p(parent):c 的父节点g(grandfather):c 的祖父节点u(uncle):p 的兄弟节点(祖父的另一个孩子)
- 先依照二叉搜索树BST规则完成节点插入,插入完毕后校验红黑树四条约束规则。
- 新节点颜色规定
- 空树首次插入:新节点设为黑色(根节点强制黑色)
- 非空树插入:新节点固定为红色;若插入黑节点会破坏「每条路径黑色节点数量相等」的规则4,修复成本极高。
- 插入后父节点为黑色:不会触发任何规则冲突,插入流程直接结束。
- 插入后父节点为红色 :违反规则3(不能存在连续红色节点)。
此时:当前节点c(红)、父节点p(红) → 祖父节点g一定是黑色;最终修复方案由叔叔节点u的状态区分处理。
① 情况1:仅变色不旋转
总模型:

- 触发条件:c(当前节点)红、p(父节点)红、g(祖父节点)黑,叔叔u存在且为红色。
处理操作:
- 父节点p、叔叔u修改为黑色
- 祖父节点g修改为红色
- 将g当作新的cur节点,向上循环校验修复
- 若g为整棵树根节点,最后强制把g改回黑色
分析:
因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加一个黑色结点,g再变红,相当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题,需要继续往上更新是因为,g是红色,如果g的父亲还是红色,那么就还需要继续处理;如果g的父亲是黑色,则处理结束了;如果g就是整棵树的根,再把g变回黑色。


② 情况2:单旋+变色
-
触发条件:c为红,p为红,g为黑,u不存在或者u存在且为黑。
-
u不存在,则c一定是新增结点;

-
u存在且为黑,则c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。

-
分析:p必须变黑,才能解决连续红色结点的问题,u不存在或者是黑色的,这里单纯的变色无法解决问题,插在p的左面 ,需要旋转+变色。
③ 情况3:双旋+变色
-
触发条件:c为红,p为红,g为黑,u不存在或者u存在且为黑。
-
u不存在,则c一定是新增结点。
-
u存在且为黑,则c一定不是新增,c之前是黑色的,是在c的子树中插入,符合情况1,变色将c从黑色变成红色,更新上来的。
-
分析:p必须变黑,才能解决连续红色结点的问题,u不存在或者是黑色的,这里单纯的变色无法解决问题,插在p的右面 ,需要双旋+变色 。


完整代码:
cpp
// 旋转代码的实现跟AVL树是一样的,只是不需要更新平衡因子
bool Insert(const pair<K, V>& kv)
{
// 树为空,直接创建根节点,根强制黑色
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
// 1、二叉搜索树标准查找插入位置
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
{
// key重复,插入失败
return false;
}
}
// 2、新建节点,新增节点默认红色
cur = new Node(kv);
cur->_col = RED;
// 挂到父节点左/右孩子
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
// 3、红黑树核心:向上调整颜色+旋转(父节点为红才需要修正)
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
// 分支1:父亲是祖父的左孩子
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
{
// 叔叔为空 / 叔叔黑色:需要旋转+变色
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
{
// 分支2:父亲是祖父的右孩子
Node* uncle = grandfather->_left;
// 情况1:叔叔存在且红,只变色上浮
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;
}
代码讲解:
①模块1:空树处理
树没有节点时,新建节点作为根,根节点必须设置黑色(红黑树硬性规则)。
②模块2:BST插入逻辑
按照二叉搜索树规则遍历寻找空位,key重复直接返回false;找到位置后新建节点默认红色。
新增节点选红色原因:红色只会破坏「不能连续红节点」规则;黑色会破坏「每条路径黑色数量一致」,修复成本更高。
③模块3:while循环 颜色&旋转修正(核心)
触发条件:父节点是红色 → 出现红红相连,违反红黑树规则,必须修正
grandfather 一定存在:父是红,父不可能是根,必然存在祖父。
分为两大对称分支:父在祖父左、父在祖父右
分支A:parent 是 grandfather 的左孩子
(1)叔叔uncle(祖父右孩子)存在且红色(情况1)
- 父、叔叔变黑,祖父变红
- 把祖父当作新cur向上循环,继续检查上层是否红红相连
- 只变色,不旋转
(2)叔叔为空 / 叔叔黑色(情况2、3)
- 形态【左左】:cur在parent左 → 对祖父单次右旋,父变黑、祖父变红
- 形态【左右】:cur在parent右 → 先左旋父,再右旋祖父(双旋),cur变黑、祖父变红
- 旋转完成直接break,结构平衡无需继续上浮
分支B:parent 是 grandfather 的右孩子(和左边镜像对称)
(1)叔叔(祖父左孩子)红色:同样变色上浮
(2)叔叔黑色/空
- 右右形态:祖父左旋(单旋)
- 右左形态:先右旋父,再左旋祖父(双旋)
- 处理完break退出循环
④模块4:收尾
无论中间怎么变色上浮,最终根节点强制赋值黑色,防止根被改成红色,保证规则永久成立。
(3)、红黑树的查找
红黑树本质是二叉搜索树,查找逻辑和普通BST完全一致:
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;
}
(4)、红黑树的验证
这里获取最长路径和最短路径,检查最长路径不超过最短路径的2倍是不可行的,因为就算满足这个条件,红黑树也可能颜色不满足规则,当前暂时没出问题,后续继续插入还是会出问题的。所以我们还是去检查4点规则,满足这4点规则,一定能保证最长路径不超过最短路径的2倍。
- 规则1枚举颜色类型,天然实现保证了颜色不是黑色就是红色。
- 规则2直接检查根即可
- 规则3前序遍历检查,遇到红色结点查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲的颜色就方便多了。
- 规则4前序遍历,遍历过程中用形参记录跟到当前结点的blackNum(黑色结点数量),前序遍历遇到黑色结点就++blackNum,走到空就计算出了一条路径的黑色结点数量。再任意一条路径黑色结点数量作为参考值,依次比较即可。

代码展示:
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;
}
// 当前节点是黑色,黑色计数+1
if (root->_col == BLACK)
{
blackNum++;
}
// 递归校验左子树 && 右子树
return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}
bool IsBalance()
{
// 空树合法
if (_root == nullptr)
return true;
// 规则2:根节点必须是黑色
if (_root->_col == RED)
return false;
// 选取最左侧一条路径的黑节点数作为标准参考值 refNum
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
// 递归整棵树校验
return Check(_root, 0, refNum);
}
讲解:
1、外层入口函数:IsBalance()
作用:对外提供接口,整体校验红黑树是否合法
- 空树直接判定合法。
- 校验规则2:根节点颜色必须为黑色,根为红直接返回false。
- 拿到基准黑点数refNum:一路走到最左叶子的空位置,统计这条路径所有黑色节点数量,当作统一标准。
- 调用递归函数Check,从头遍历整树校验。
2、递归校验函数:Check
参数说明
- root:当前遍历节点
- blackNum:走到当前节点累计的黑色数目
- refNum:全局统一的标准黑色数量(最左路径算出)
① 递归终止条件(root == nullptr)
一条从根到空叶子的完整路径遍历完成
对比本条路径的blackNum 和标准refNum,不一致 → 违反规则4(所有路径黑色节点数目相同),报错返回false;相等则正常返回true。
② 校验规则3:禁止连续红色节点
如果当前节点是红色,同时其父节点也是红色,出现红红相连,红黑树非法,打印错误信息并返回false。
设计思路:不用判断孩子节点,只看自身+父节点,写法更加简洁。
③ 累加黑色计数
当前节点是黑色,blackNum += 1;红色不计数。
④ 左右子树递归校验
左、右子树必须全部校验通过(&&关系),整棵树才算合法。
三、完整代码
RBTree.h
cpp
#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>* _parent;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
Colour _col;
RBTreeNode(const pair<K,V>& kv)
:_kv(kv)
,_parent(nullptr)
, _left(nullptr)
, _right(nullptr)
{ }
};
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 (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_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)
{
// g
// p u
// c
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
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)
{
// g
// u p
// c
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
//右单旋
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* parentP = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (parentP->_left == parent)
{
parentP->_left = subL;
}
else
{
parentP->_right = subL;
}
subL->_parent = parentP;
}
}
//左单旋
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* parentP = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (parentP == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parent == parentP->_left)
{
parentP->_left = subR;
}
else
{
parentP->_right = subR;
}
subR->_parent = parentP;
}
}
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;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
int Height()
{
return _Height(_root);
}
int Size()
{
return _Size(_root);
}
bool IsBalanceTree()
{
// 空树合法
if (_root == nullptr)
return true;
// 规则2:根节点必须是黑色
if (_root->_col == RED)
return false;
// 选取最左侧一条路径的黑节点数作为标准参考值 refNum
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
// 递归整棵树校验
return Check(_root, 0, refNum);
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
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;
}
int _Size(Node* root)
{
if (root == nullptr)
return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}
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;
}
// 当前节点是黑色,黑色计数+1
if (root->_col == BLACK)
{
blackNum++;
}
// 递归校验左子树 && 右子树
return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum);
}
private:
Node* _root = nullptr;
};
test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"RBTree.h"
#include<vector>
// 测试代码
void TestAVLTree1()
{
RBTree<int, int> t;
// 常规的测试用例
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
// 特殊的带有双旋场景的测试用例
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto e : a)
{
t.Insert({ e, e });
}
t.InOrder();
cout << t.IsBalanceTree() << endl;
}
// 插入一堆随机值,测试平衡,顺便测试一下高度和性能等
void TestAVLTree2()
{
const int N = 100000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand() + i);
}
size_t begin2 = clock();
RBTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
}
size_t end2 = clock();
cout << "Insert:" << end2 - begin2 << endl;
cout << t.IsBalanceTree() << endl;
cout << "Height:" << t.Height() << endl;
cout << "Size:" << t.Size() << endl;
size_t begin1 = clock();
//确定在的值
/*for (auto e : v)
{
t.Find(e);
}*/
// 随机值
for (size_t i = 0; i < N; i++)
{
t.Find((rand() + i));
}
size_t end1 = clock();
cout << "Find:" << end1 - begin1 << endl;
}
int main()
{
//TestAVLTree1();
TestAVLTree2();
return 0;
}