文章目录
红黑树的概念
一、红黑树的性质
红黑树是一种自平衡的二叉搜索树。它的节点被标记为红色或黑色,通过特定的规则来保持树的近似平衡(最长路径不超过最短路径的二倍)。其主要规则:
- 每个节点要么是红色,要么是黑色。
- 根节点是黑色的。
- 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 [每条路径的黑色节点的数量是相等的]
- 如果一个节点是红色的,那么它的两个子节点都是黑色的。 [也就是一条路径中,没有连续的红色节点]
- 每个叶子节点(NIL 节点)是黑色的。 [如图中所示,就是将所有空节点当作是黑色节点,不是很重要]
注:在数路径 的时候要以空节点为结束去数路径,如图有11条路经
红黑树原理
那么红黑树是如何凭借颜色来控制平衡的呢?
我们想在不违反红黑树规则,极端情况如下图【注:这种情况不一定出现,用来方便 理解】
最短的路径:全是黑色 最长的路径:一黑一红
那么我们设最短路径为n,最长路径也就是2n,即这张图中最长路径就是最短路径的2倍。
而还可以是这种情况如下图
这种情况就是最短路径的2倍大于最长路径。
所以这就是红黑树的近似平衡(即最长路径不超过最短路径的二倍)
而我们这里的最长最短路径是在红黑树规则理论上而言,实际的红黑 树中最长最短不要低估是上米娜的图所示。
二、红黑树的优势和比较
红黑树之所以在众多数据结构中脱颖而出,是因为它在插入和删除操作时能够在 O(log n)
的时间复杂度内自动调整,保持平衡,从而保证了高效的查找、插入和删除性能。
以AVL树做对比:
平衡机制:
- AVL 树通过严格的平衡条件(左右子树的高度差绝对值不超过 1)来保持平衡。每次插入或删除操作后,如果导致树不平衡,需要进行复杂的旋转操作来重新平衡树。
- 红黑树的平衡条件相对宽松,通过节点颜色的约束(红黑规则)来保持大致平衡。插入或删除操作后,调整平衡的操作相对较简单。
性能:
- 在频繁插入和删除操作的情况下,红黑树的性能通常优于 AVL 树。因为 AVL 树的平衡调整更为频繁且复杂,可能导致更多的开销。
- 对于查找操作,AVL 树由于其更严格的平衡特性,可能会稍微快一些。
空间复杂度:
- AVL 树的每个节点需要额外存储平衡因子信息,空间复杂度相对较高。
- 红黑树只需存储颜色信息,空间复杂度相对较低。
红黑树的模拟实现
构建红黑树的数据结构定义节点的基本结构和初始化方式
cpp
enum Color
{
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;//该节点的父节点
Color _col;
RBTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
{}
};
先定义一个枚举类型 Color
和一个模板结构体 RBTreeNode
。
-
枚举类型储存红黑两种颜色。
-
pair<K, V> _kv
:用于存储键值对数据。 -
RBTreeNode<K, V>* _left
、RBTreeNode<K, V>* _right
和RBTreeNode<K, V>* _parent
:分别指向该节点的左孩子、右孩子和父节点,构建了树结构中的节点关系。 -
Color _col
:使用前面定义的枚举类型来表示节点的颜色属性。 -
结构体中的构造函数
RBTreeNode(const pair<K, V>& kv)
用于初始化节点对象,将传入的键值对赋值给_kv
,并将指针_left
、_right
和_parent
初始化为nullptr
。
插入新节点
插入新节点的颜色
首先,按照二叉搜索树的插入规则,将新节点插入到合适的位置。
新插入的节点默认是红色,原因:
如果我们插入红色节点我们破坏规则4(如果一个节点是红色的,那么它的两个子节点都是黑色的,也就是一条路径中,没有连续的红色节点)
如果我们插入黑色节点我们破坏规则3(从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点)
那么我们插入黑色节点的时候规则3必然会被破坏,而插入红色节点如果父节点是黑色则没有事情,如果父节点是红色才会破坏规则4==,所以相比之下插入红色节点更好。
调整颜色和结构以满足红黑树性质
- 如果插入节点的
父节点是黑色
,那么无需做任何调整,树已经满足红黑树的性质。- 如果插入节点的
父节点是红色
,那么会出现违反红黑树性质的情况,需要进行调整。- 情况一:父节点的
兄弟节点是黑色
,且父节点是祖父节点的左孩子
- 情况 1.1:插入节点是父节点的右孩子
对父节点进行左旋,将当前情况转换为情况 1.2。 - 情况 1.2:插入节点是父节点的左孩子
将父节点染成黑色,祖父节点染成红色,对祖父节点进行右旋。
- 情况 1.1:插入节点是父节点的右孩子
- 情况二:父节点的
兄弟节点是黑色
,且父节点是祖父节点的右孩子
- 情况 2.1:插入节点是父节点的左孩子
对父节点进行右旋,将当前情况转换为情况 2.2。 - 情况 2.2:插入节点是父节点的右孩子
将父节点染成黑色,祖父节点染成红色,对祖父节点进行左旋。
- 情况 2.1:插入节点是父节点的左孩子
- 情况三:父节点的
兄弟节点是红色
将父节点和兄弟节点染成黑色,祖父节点染成红色,以祖父节点为新的插入节点继续调整。
- 情况一:父节点的
- 如果插入节点的
cpp
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)
{
//如果u存在,cur和parent都是红,grandfather是黑
//p,u变成黑,g变成红,g当成cur向上调整
//u存在且是红 -> 变色进行调整
Node* grandfather = parent->_parent;
//p在g的左边
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
{
//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
{
//u在g的左边
// g
// u p
Node* uncle = grandfather->_left;
//u存在且为红 -> 直接变色
if (uncle&&uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else
{
//u存在且为黑或者不存在 -> 旋转加变色
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;
}
红黑树的应用场景
红黑树在许多领域都有广泛的应用,比如:
数据库中的索引结构。
各种编程语言的标准库中,如 Java 中的 TreeMap 和 TreeSet 。
总之,红黑树作为一种高效且强大的数据结构,对于优化程序性能和提高数据管理效率具有重要意义。深入理解和掌握红黑树,将为我们在计算机科学领域的探索提供有力的支持。