前面我们讲了C语言的基础知识,也了解了一些初阶数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,也了解了C++中的模版,以及学习了几个STL的结构,也了解了搜索二叉树的基本原理,以及学习了数据结构的大哥AVL树相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点—— 红黑树(自平衡二叉搜索树) 。下面话不多说坐稳扶好咱们要开车了
红黑树(Red-Black Tree)是一种自平衡的二叉查找树,红黑树之所以称为"红黑",是因为每个节点上都有一个颜色属性,可以是红色或黑色。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
NIL
节点)都是黑色的。注意:NIL
节点是一种特殊的节点,表示空节点或叶子节点。它被视为黑色节点,并且没有实际存储的值。
// 颜色枚举类型,表示节点颜色属性
enum Colour
{
RED, // 红色
BLACK, // 黑色
};
// 红黑树节点结构体模板
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left; // 左子节点指针
RBTreeNode<T>* _right; // 右子节点指针
RBTreeNode<T>* _parent; // 父节点指针
T _data; // 节点数据
Colour _col; // 节点颜色属性,取值为 RED 或 BLACK
// 构造函数,初始化节点的左右子节点、父节点和颜色属性
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED) // 新插入节点的颜色总是红色(符合红黑树性质)
{}
};
⭕它表示红黑树中的一个节点。该结构体包含以下成员:
_left
:指向左子节点的指针。_right
:指向右子节点的指针。_parent
:指向父节点的指针。_data
:存储节点的数据。_col
:标识节点的颜色属性,可以是 RED(红色)或 BLACK(黑色)。结构体模板还有一个构造函数,用于初始化节点的各个成员。构造函数接受一个参数 data
,表示要存储在节点中的数据。构造函数会将左右子节点指针、父节点指针和节点颜色属性初始化为默认值,其中新插入的节点颜色总是红色。
红黑树的每个节点都是通过该结构体创建的实例,通过连接不同节点的指针,可以构建出红黑树的结构。
带有头结点的红黑树是在普通红黑树的基础上添加了一个额外的头结点(也称为哨兵节点),用于简化红黑树的操作和边界情况的处理。**头结点通常被设置为黑色并且不存储任何实际数据。**具体来说,当红黑树为空时,根节点指针指向头结点,而不是空指针。这样,在对红黑树进行插入、删除等操作时,可以直接操作根节点,无需检查根节点是否为空。头结点的左子节点指向实际的根节点,而右子节点指向树中的最大节点(即最右侧的节点)。这样,通过头结点可以方便地访问到根节点以及整个树的边界。
总之,带有头结点的红黑树通过引入额外的头结点,简化了红黑树的操作和边界情况的处理,提高了代码的可读性和可维护性。
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);
// 进行变换操作使树满足红黑树性质
// ... 缺少变换操作的代码 ...
// 插入成功
return true;
}
cur
为新插入的节点,接下来针对这一个节点进行讨论变换(颜色)就可以了
⭕因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论。
约定:cur为新插入节点,p为父节点,g为祖父节点,u为叔叔节点
如果g节点,调整完成后,需要将g改为黑色
如果g是子树,g一定有双亲,如果g的双亲是红色,需要继续向上调整
解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。
解决办法
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为新插入的节点,接下来针对这一个节点进行讨论变换(颜色)就可以了
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
// 情况1:u存在且为红,变色处理,并继续往上处理
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上调整
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:u不存在/u存在且为黑,旋转+变色
{
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
//parent->_col = RED;
grandfather->_col = RED;
}
break;
}
}
else // (grandfather->_right == parent)
{
// g
// u p
// c
Node* uncle = grandfather->_left;
// 情况1:u存在且为红,变色处理,并继续往上处理
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上调整
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:u不存在/u存在且为黑,旋转+变色
{
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppnode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
}
如果待删除节点有两个子节点,需要先寻找其后继节点(即右子树中最小的节点或左子树中最大的节点),将其替换到待删除节点的位置,然后再删除该后继节点。
在实现过程中,需要注意以下几点:
下面是一个实现删除红黑树节点的代码示例:
bool Remove(const K& key)
{
// 查找要删除的节点
pair<Node*, bool> res = Find(key);
if (!res.second)
{
return false; // 没有找到要删除的节点
}
Node* cur = res.first;
if (cur->_left && cur->_right) // 待删除节点有两个子节点
{
Node* next = cur->_right;
while (next->_left) // 找到待删除节点的后继节点
{
next = next->_left;
}
cur->_kv = next->_kv; // 将后继节点的值赋给待删除节点
cur = next; // 准备删除后继节点
}
Node* child = cur->_left ? cur->_left : cur->_right; // 获取待删除节点的唯一子节点
if (cur->_col == BLACK) // 如果待删除节点为黑色节点,则需要进行额外的操作
{
cur->_col = child ? child->_col : RED; // 将待删除节点的颜色赋给其子节点(若无子节点则为红色)
FixUp(cur); // 进行变换操作,维护树的性质
}
Replace(cur, child); // 删除节点
return true;
}
在该示例中,Find
函数用于查找要删除的节点,如果未找到则返回 false
。如果待删除节点有两个子节点,则需要寻找其后继节点,并将后继节点的值赋给待删除节点。然后,判断待删除节点的颜色,如果为黑色节点,则需要进行额外的操作以维护树的性质。最后,调用 Replace
函数删除节点,并返回 true
表示删除成功。
平衡性:
性能:
存储空间:
注意:红黑树适用于插入和删除操作较频繁的场景,而AVL树适用于对平衡性要求更高的场景。
红黑树是一种高效的平衡二叉搜索树,具有较好的查找、插入和删除性能。因此,它在许多方面都有广泛的应用。
C++ STL中的map和set容器:STL库中的map和set容器都是基于红黑树实现的,它们提供了快速的键值查找和排序的功能。
数据库索引结构:许多数据库系统采用B+树或B树作为索引结构,但也有一些数据库系统采用红黑树作为索引结构来加速数据的查找。
操作系统调度:操作系统CPU调度算法中也使用了红黑树。例如,在Linux内核中,用红黑树来维护进程的优先级队列。
编译器标识符表:编译器在分析代码时需要维护一个标识符表来存放变量、函数等信息。红黑树可以作为标识符表的底层数据结构,提高符号查找的效率。
计算机网络路由表:路由表是计算机网络中进行数据包转发的重要组成部分之一。为了快速查找路由路径,许多路由器会使用红黑树等数据结构来维护路由表。
#pragma once
enum Colour
{
RED,
BLACK,
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Colour _col;
RBTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(RED)
{}
};
template<class K, class T, class KeyOfT>
class RBTree
{
typedef RBTreeNode<T> Node;
public:
~RBTree()
{
_Destroy(_root);
_root = nullptr;
}
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;
}
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为新插入的节点,接下来针对这一个节点进行讨论变换(颜色)就可以了
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
if (grandfather->_left == parent)
{
Node* uncle = grandfather->_right;
// 情况1:u存在且为红,变色处理,并继续往上处理
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上调整
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:u不存在/u存在且为黑,旋转+变色
{
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else
{
// g
// p u
// c
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
//parent->_col = RED;
grandfather->_col = RED;
}
break;
}
}
else // (grandfather->_right == parent)
{
// g
// u p
// c
Node* uncle = grandfather->_left;
// 情况1:u存在且为红,变色处理,并继续往上处理
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 继续往上调整
cur = grandfather;
parent = cur->_parent;
}
else // 情况2+3:u不存在/u存在且为黑,旋转+变色
{
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandfather);
grandfather->_col = RED;
parent->_col = BLACK;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
}
bool IsBalance()
{
if (_root && _root->_col == RED)
{
cout << "根节点颜色是红色" << endl;
return false;
}
int benchmark = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++benchmark;
cur = cur->_left;
}
// 连续红色节点
return _Check(_root, 0, benchmark);
}
int Height()
{
return _Height(_root);
}
private:
void _Destroy(Node* root)
{
if (root == nullptr)
{
return;
}
_Destroy(root->_left);
_Destroy(root->_right);
delete root;
}
int _Height(Node* root)
{
if (root == NULL)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
bool _Check(Node* root, int blackNum, int benchmark)
{
if (root == nullptr)
{
if (benchmark != blackNum)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++blackNum;
}
if (root->_col == RED
&& root->_parent
&& root->_parent->_col == RED)
{
cout << "存在连续的红色节点" << endl;
return false;
}
return _Check(root->_left, blackNum, benchmark)
&& _Check(root->_right, blackNum, benchmark);
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppnode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
}
private:
Node* _root = nullptr;
};
感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!