【C++】红黑树:使用及实现
目录
一 红黑树的概念
二 红黑树的规则
1 规则说明
2 思考⼀下,红黑树如何确保最长路径不超过最短路径的2倍的?
3 红黑树的效率
4 红黑树新增节点
(1)只用变色-----父节点的兄弟节点存在
(2)旋转+变色----父节点的兄弟节点不存在
5 红黑树计算路径数量
三 红黑树的实现
1 红黑树的结构
2 红黑树的插入
(1) 红黑树树插入一个值的大概过程
(2)情况一:变色
(3)情况二:单旋+变色
(4)情况三:双旋+变色
3 红黑树插入代码实现
(1)旋转代码
(2)插入代码
四 红黑树的查找
五 红黑树的验证
(1)思路
(2)代码实现
六 完整代码
一 红黑树的概念
红黑树是一棵二叉搜索树,他的每个结点增加一个存储位来表示结点的颜色,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而接近平衡的。
最长路径 <= 2*最短路径
二 红黑树的规则
1 规则说明
1. 每个结点不是红色就是黑色
2. 根结点是黑色的
3. 如果一个结点是红色的,则它的两个孩子结点必须是黑色的,也就是说任意一条路径不会有连续的红色结点。
4. 对于任意一个结点,从该结点到其所有NULL结点的简单路径上,均包含相同数量的黑色结点。
说明:《算法导论》等书籍上补充了一条每个叶子结点 (NIL) 都是黑色的规则。这里所指的叶子结点不是传统意义上的叶子结点,而是我们说的空结点,有些书籍上也把 NIL 叫做外部结点。NIL 是为了方便准确地标识出所有路径,《算法导论》在后续讲解实现的细节中也忽略了 NIL 结点,所以我们了解一下这个概念即可。
2 思考⼀下,红黑树如何确保最长路径不超过最短路径的2倍的?
- 由规则4可知,从根到NULL结点的每条路径都有相同数量的黑色结点。所以极端场景下,最短路径就是全是黑色结点的路径,假设最短路径长度为bh(black height,黑高)。
- 由规则2和规则3可知,任意一条路径不会有连续的红色结点。所以极端场景下,最长的路径就是一黑一红间隔组成,那么最长路径的长度为2×bh。
- 综合红黑树的4点规则而言,理论上的全黑最短路径和一黑一红的最长路径并不是在每棵红黑树都存在的。假设任意一条从根到NULL结点路径的长度为h,那么bh ≤ h ≤ 2×bh。
理论上的最长路径和最短路径不一定存在
3 红黑树的效率


4 红黑树新增节点
新增节点的时候:要新增红色的节点(因为新增黑色节点,会破坏每条路径上的黑色节点数量相同这个规则)
当我们新增红色的节点时,发现如果新增节点的父节点也是红色,那么就违反了规则3.此时我们就需要变色。
(1)只用变色-----父节点的兄弟节点存在

当我们在节点28的左子树插入时,只能插入红色,这个时候就需要变色。我们设当前插入的节点为cur(简写为c),它的父节点为p,父节点的兄弟节点为u,父节点的父节点为g

将c的父亲节点和兄弟节点都变成黑色,爷爷节点(g)变成红色,我们发现上图中红色圈的子树实现了暂时的平衡,但是p节点上面的节点颜色又相悖,所以我们需要继续调整变色,将现在的g姐弟啊设为c节点,它的父节点为p,父节点的兄弟节点为u,父节点的父节点为g。具体调整代码和思路我们稍后讲解。
(2)旋转+变色----父节点的兄弟节点不存在

我们发现,如果插入节点的父节点没有兄弟节点,那此时c节点所处的子树不再平衡,需要先进行左旋,再变色,具体实现思路我们下面讲解。
5 红黑树计算路径数量
计算路径数量时,要数到空节点的地方,所以下图的路径数量不是3,而是6

三 红黑树的实现
1 红黑树的结构
// 枚举值表⽰颜⾊
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 红黑树的插入
(1) 红黑树树插入一个值的大概过程
1. 插入一个值按二叉搜索树规则进行插入,插入后我们只需要观察是否符合红黑树的4条规则。
2. 如果是空树插入,新增结点是黑色结点。如果是非空树插入,新增结点必须红色结点,因为非空树插入,新增黑色结点就破坏了规则4,规则4是很难维护的。
3. 非空树插入后,新增结点必须红色结点,如果父亲结点是黑色的,则没有违反任何规则,插入结束。
4. 非空树插入后,新增结点必须红色结点,如果父亲结点是红色的,则违反规则3。进一步分析,c是红色,p为红,g必为黑,这三个颜色都固定了,关键的变化看u的情况,需要根据u分为以下几种情况分别处理。 说明:下图中假设我们把新增结点标识为c(cur),c的父亲标识为p(parent),p的父亲标识为g(grandfather),p的兄弟标识为u(uncle)。
(2)情况一:变色
c为红,p为红,g为黑,u存在且为红,则将p和u变黑,g变红。再把g当做新的c,继续往上更新。
分析:因为p和u都是红色,g是黑色,把p和u变黑,左边子树路径各增加一个黑色结点,g再变红,相当于保持g所在子树的黑色结点的数量不变,同时解决了c和p连续红色结点的问题,需要继续往上更新是因为,g是红色,如果g的父亲还是红色,那么就还需要继续处理;如果g的父亲是黑色,则处理结束了;如果g就是整棵树的根,再把g变回黑色。
情况1只变色,不旋转。所以无论c是p的左还是右,p是g的左还是右,都是上面的变色处理方式。

• 跟AVL树类似,图0我们展示了一种具体情况,但是实际中需要这样处理的有很多种情况。
• 图1将以上类似的处理进行了抽象表达,d/e/f代表每条路径拥有hb个黑色结点的子树,a/b代表每条路径拥有hb-1个黑色结点的根为红的子树,hb>=0。
• 图2/图3/图4,分别展示了hb==0/hb==1/hb==2的具体情况组合分析,当hb等于2时,这里组合情况上百亿种,这些样例是帮助我们理解,不论情况多少种,多么复杂,处理方式一样的,变色再继续往上处理即可,所以我们只需要看抽象图即可。




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

如果p是g的左,c是p的左,那么以g为旋转点进行右单旋,再把p变黑,g变红即可。p变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则。

如果p是g的右,c是p的右,那么以g为旋转点进行左单旋,再把p变黑,g变红即可。p变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为p的父亲是黑色还是红色或者空都不违反规则。


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

如果p是g的左,c是p的右,那么先以p为旋转点进行左单旋,再以g为旋转点进行右单旋,再把c变黑,g变红即可。c变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则。

如果p是g的右,c是p的左,那么先以p为旋转点进行右单旋,再以g为旋转点进行左单旋,再把c变黑,g变红即可。c变成这颗树新的根,这样子树黑色结点的数量不变,没有连续的红色结点了,且不需要往上更新,因为c的父亲是黑色还是红色或者空都不违反规则。


3 红黑树插入代码实现
(1)旋转代码
红黑树的旋转代码实现和AVL树的是一样的,只是不需要平衡因子
void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}
(2)插入代码
// 红黑树插入实现(旋转逻辑与AVL树一致,无需维护平衡因子)
bool Insert(const pair<K, V>& kv)
{// 情况1:空树插入,新节点直接作为根,根必为黑色if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}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 // 键已存在,插入失败{return false;}}// 创建新节点,非空树插入必为红色(避免破坏规则4)cur = new Node(kv);cur->_col = 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; // 祖父必为黑色(无连续红)// 场景1:父节点是祖父的左孩子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;}else // 子场景1.2:叔叔不存在或为黑色(需旋转+变色){// 子情况1.2.1:cur是父节点的左孩子(左左结构,右单旋)if (cur == parent->_left){RotateR(grandfather); // 以祖父为轴右旋parent->_col = BLACK; // 旋转后父节点成新根,设黑grandfather->_col = RED; // 原祖父设红}else // 子情况1.2.2:cur是父节点的右孩子(左右结构,双旋){RotateL(parent); // 先以父为轴左旋,转为左左结构RotateR(grandfather); // 再以祖父为轴右旋cur->_col = BLACK; // 旋转后cur成新根,设黑grandfather->_col = RED; // 原祖父设红}break; // 旋转+变色后冲突解决,无需向上回溯}}else // 场景2:父节点是祖父的右孩子(与场景1对称){Node* uncle = grandfather->_left; // 叔叔是祖父的左孩子// 子场景2.1:叔叔存在且为红色(仅变色)if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK; // 父、叔变黑grandfather->_col = RED; // 祖父变红cur = grandfather; // 向上回溯parent = cur->_parent;}else // 子场景2.2:叔叔不存在或为黑色(需旋转+变色){// 子情况2.2.1:cur是父节点的右孩子(右右结构,左单旋)if (cur == parent->_right){RotateL(grandfather); // 以祖父为轴左旋parent->_col = BLACK; // 父节点成新根,设黑grandfather->_col = RED; // 原祖父设红}else // 子情况2.2.2:cur是父节点的左孩子(右左结构,双旋){RotateR(parent); // 先以父为轴右旋,转为右右结构RotateL(grandfather); // 再以祖父为轴左旋cur->_col = BLACK; // cur成新根,设黑grandfather->_col = RED; // 原祖父设红}break; // 冲突解决,退出循环}}}// 最终保障:根节点必为黑色(修复过程中可能被设为红色)_root->_col = BLACK;return true;
}
四 红黑树的查找
按⼆叉搜索树逻辑实现即可,搜索效率为O(logN)
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;
}
五 红黑树的验证
(1)思路
红黑树验证逻辑:优先检查规则,而非路径长度
- 直接检查「最长路径 ≤ 最短路径 ×2」不可行:即便满足该条件,红黑树可能仍违反颜色规则,当前无问题不代表后续插入不会出错。因此必须通过验证 4 条核心规则来确保树的合法性,且满足 4 条规则后,天然能保证「最长路径不超过最短路径的 2 倍」。
4 条规则的验证方式
- 规则 1(颜色合法性):通过枚举颜色类型(仅红、黑两种),天然保证节点颜色非黑即红,无需额外复杂检查。
- 规则 2(根节点为黑):直接访问根节点,检查其颜色是否为黑色即可,逻辑最简单。
- 规则 3(无连续红节点):优先前序遍历验证,不直接检查红色节点的子节点(子节点可能不存在,需处理空指针),而是反过来检查每个节点的父节点 —— 若当前节点为红色,只需确认其父节点是否为黑色,避免复杂的子节点存在性判断。
- 规则 4(路径黑节点数相等):前序遍历过程中,用形参
blackNum记录「根节点到当前节点的黑色节点数量」,遇到黑色节点则blackNum++;当遍历至空节点时,当前blackNum即为该路径的黑节点总数。选取任意一条路径的黑节点数作为参考值,后续所有路径的黑节点数均需与参考值一致,即可验证规则 4。
我们在判断每条路径上的黑色节点数是否相同时,可以先序遍历完第一条路径,然后拿这条路径和其他的路径相比较黑色节点数是否相同

(2)代码实现
public部分:
void InOrder(){_InOrder(_root);cout << endl;}bool IsBalanceTree(){if (_root && _root->_col == RED)return false;// 最左路径黑色节点的数量做参考值去比较其他路径int left_bn = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)left_bn++;cur = cur->_left;}return _CheckColour(_root, 0, left_bn);}int Height(){return _Height(_root);}int Size(){return _Size(_root);}
private部分:
private:int _Size(Node* root){if (root == nullptr)return 0;return _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;}// root_cur_bn 根到当前节点路径上黑色节点的数量// 前序递归bool _CheckColour(Node* root, int root_cur_bn, const int left_bn){if (root == nullptr){// 检查每条路径的黑色节点的数量if (root_cur_bn != left_bn){cout << "黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){root_cur_bn++;}// 检查连续的红色节点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "存在连续红色节点" << endl;return false;}return _CheckColour(root->_left, root_cur_bn, left_bn)&& _CheckColour(root->_right, root_cur_bn, left_bn);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);//cout << root->_kv.first << ":" << root->_kv.second << endl;cout << root->_kv.first <<" ";_InOrder(root->_right);}private:Node* _root = nullptr;
六 完整代码
#pragma once#pragma once
#include<assert.h>enum Colour
{RED,BLACK
};// red black tree
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){}
};bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){
_root = new Node(kv);_root->_col = BLACK;return true;}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{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;// g// p uif (parent == grandfather->_left){Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// u存在且为红 -》变⾊再继续往上处理 parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{// u存在且为⿊或不存在 -》旋转+变⾊ 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{// g// u pNode* uncle = grandfather->_left;// 叔叔存在且为红,-》变⾊即可 if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理 cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且为⿊ {// 情况⼆:叔叔不存在或者存在且为⿊ // 旋转+变⾊ // g// u p// cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// u p// cRotateR(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* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* parentParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parentParent == nullptr){_root = subR;subR->_parent = nullptr;}else{if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void InOrder(){_InOrder(_root);cout << endl;}Node* Find(const K& key){KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool IsBalanceTree(){if (_root && _root->_col == RED)return false;// 最左路径黑色节点的数量做参考值去比较其他路径int left_bn = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)left_bn++;cur = cur->_left;}return _CheckColour(_root, 0, left_bn);}int Height(){return _Height(_root);}int Size(){return _Size(_root);}private:
int _Size(Node* root){if (root == nullptr)return 0;return _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;}// root_cur_bn 根到当前节点路径上黑色节点的数量// 前序递归
bool _CheckColour(Node* root, int root_cur_bn, const int left_bn){if (root == nullptr){// 检查每条路径的黑色节点的数量if (root_cur_bn != left_bn){cout << "黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){root_cur_bn++;}// 检查连续的红色节点if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "存在连续红色节点" << endl;return false;}return _CheckColour(root->_left, root_cur_bn, left_bn)&& _CheckColour(root->_right, root_cur_bn, left_bn);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);//cout << root->_kv.first << ":" << root->_kv.second << endl;cout << root->_kv.first <<" ";_InOrder(root->_right);}private:Node* _root = nullptr;
};
