红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的。
红黑树性质规则 :
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
// 枚举值表示颜色
enum Colour
{
RED,
BLACK
};
template
struct RBTreeNode
{
pair _kv;
RBTreeNode* _left;
RBTreeNode* _right;
RBTreeNode* _parent;
Colour _col;
RBTreeNode(const pair& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_col(RED)
{}
};
实际上为了后续实现关联式容器简单,红黑树的实现中增加一个头结点,因为跟节点必须为黑色,为了 与根节点进行区分,将头结点给成黑色,并且让头结点的 pParent 域指向红黑树的根节点,pLeft 域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点
但是这里为了方便实现(也更方便理解红黑树的规则),根结点的父亲结点是指向空的
template
class RBTree { typedef RBTreeNode Node; public: bool Insert(const pair & kv); private: // 左单旋 void RotateL(Node* parent); // 右单旋 void RotateR(Node* parent); Node* _root = nullptr; };
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
1. 按照二叉搜索的树规则插入新节点
2. 检测新节点插入后,红黑树的性质是否造到破坏(着重理解)
1.因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何 性质,则不需要调整
2.当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连 在一起的红色节点,此时需要对红黑树分情况来讨论:
情况一: cur为红,parent为红,grandparent为黑,uncle存在且为红(进行变色处理)
解决方式:
将p,u改为黑,g改为红,然后把g当成cur,继续向上调整
需要注意的是:
要判断parent是grand的左孩子还是右孩子。
情况二: cur为红,p为红,g为黑,u不存在或者u存在且为黑(旋转+变色)
解决方式:
1.parent为grand的左孩子时:
若cur为parent的左孩子,则进行对grand右单旋转;
若cur为parent的右孩子,则针对parent做左单旋转,再对grand右单旋转;
2.parent为grand的右孩子时
若cur为parent的右孩子,则进行grand左单旋转;
若cur为parent的左孩子,则针对parent做右单旋转,再对grand左单旋转;
插入实现代码如下:
(旋转的理解可以看我AVL树的博客)
bool Insert(const pair& 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);
//将插入结点的值进行连接
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else if (parent->_kv.first < kv.first)
{
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) //uncle存在且为红,进行变色处理
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else //uncle不存在 或者 uncle存在且为黑,旋转+变色
{
if (cur == parent->_left)
{
//旋转
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else // cur == parent->_right
{
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //旋转+变色 之后 已经处理完成
}
}
else //parent == grandfather->_right
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else //uncle不存在 或者为黑
{
if (cur == parent->_left)
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
else
{
RotateL(grandfather);
parent->_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;
}
Node* Pparent = parent->_parent;
cur->_left = parent;
parent->_parent = cur;
if (Pparent == nullptr)
{
cur->_parent = nullptr;
}
else //父亲的父亲不是空
{
if (Pparent->_kv.first < cur->_kv.first)
{
Pparent->_right = cur;
}
else
{
Pparent->_left = cur;
}
cur->_parent = Pparent;
}
}
// 右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = curright;
if (curright)
{
curright->_parent = parent;
}
Node* Pparent = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (Pparent == nullptr)
{
cur->_parent = nullptr;
}
else
{
if (Pparent->_kv.first < cur->_kv.first)
{
Pparent->_right = cur;
}
else
{
Pparent->_left = cur;
}
cur->_parent = Pparent;
}
}
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质(代码展示)
bool IsBalanceTree() { if (_root == nullptr) return true; if (_root->_col == RED) return false; // 参考值,查找从根到叶子的黑结点数量 int refNum = 0; Node* cur = _root; while (cur) { if (cur->_col == BLACK) { ++refNum; } cur = cur->_left; } //查找从根结点到叶子结点上的每一条路径的黑结点数是否一致 return Check(_root, 0, refNum); } // 前序递归遍历 bool Check(Node* root, int blackNum, const int refNum) { if (root == nullptr) { // 前序遍历走到空时,意味着一条路径走完了 //cout << blackNum << endl; if (refNum != blackNum) { cout << "存在黑色结点的数量不相等的路径" << endl; return false; } return true; } // 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了 if (root->_col == RED && root->_parent && root->_parent->_col == RED) { cout << root->_kv.first << "存在连续的红色结点" << endl; return false; } if (root->_col == BLACK) { blackNum++; } return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum); }
一、定义与核心特性
红黑树是一种自平衡的二叉搜索树,通过颜色标记(红/黑)和旋转操作维护近似平衡,确保查找、插入、删除操作的时间复杂度为O(logn)。其核心特性由以下五点构成:
- 根节点为黑色;
- 叶子节点(NIL节点)为黑色;
- 红色节点的子节点必须为黑色(即父子节点不能同时为红)
- 从任一节点到其所有叶子节点的路径中,黑色节点数量相同;
- 新插入的节点默认为红色,后续通过调整满足规则。
二、运行规则:插入与删除调整
当插入或删除节点破坏红黑树特性时,通过以下操作调整:
- 颜色变换:例如将父节点和叔节点由红变黑,祖父节点由黑变红;
- 旋转操作:
- 左旋:以某节点为支点,右子节点上升为父节点;
- 右旋:以某节点为支点,左子节点上升为父节点;
- 组合调整:如插入时可能需多次旋转+变色(如“红-黑-红”结构调整为全黑)。
三、与AVL树的对比
- 平衡标准:AVL树要求左右子树高度差≤1,而红黑树允许一定程度的不平衡,但通过特性4限制树高;
- 调整频率:AVL树插入/删除后需频繁调整,红黑树调整次数更少,综合性能更优;
- 适用场景:红黑树适合频繁修改的数据集(如内存数据库),AVL树适合读多写少的场景。
- 红黑树不追 求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红 黑树更多。
四、应用场景
红黑树广泛应用于需要高效动态数据管理的场景:
- 编程语言库:如Java的
TreeMap
、C++ STL的map
和set
;- 文件系统:如Ext3/Ext4的目录索引;
- 实时计算:保证操作时间稳定性的任务调度。