🔥🔥 欢迎来到小林的博客!!
🛰️博客主页:✈️小林爱敲代码
🛰️博客专栏:✈️数据结构与算法
🛰️欢迎关注:👍点赞🙌收藏✍️留言
今天给大家讲解红黑树,和AVL树一样,这章暂且不讲删除。后续有时间会为大家带来红黑树的删除操作。
每日一句: 生活原本沉闷,但跑起来就会有风。
💖1. 红黑树的概念
红黑树,是一种二叉搜索树,与AVL树不同的是,它在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。因此红黑树相比于AVL树,没有那么平衡。30层高度的AVL树换成红黑树可能会有60层,但查找30次和60次并没有什么区别。AVL树的平衡是付出了代价的,它旋转的次数更多了。而红黑树也是平衡的,它旋转的次数并没有AVL多。
💖2. 红黑树的性质
红黑树必须满足以下五点性质,有一条不满足,就不是红黑树了。
1.每个节点不是红色就是黑色
2.根节点是黑色的
3.红节点的孩子必须是黑色
4.每条路径黑色节点的数量相同
5.空节点都是黑色的
满足上面性质的红黑树,它的最长路径中节点的个数不会超过最短路径的2倍
那我们先来观赏一颗红黑树

我们可以发现,
每个节点不是黑色就是红色,满足条件1
根节点 50 是黑色的,满足条件2
红色节点的孩子都是黑色,满足条件3
每条路径都有2个黑色节点,满足条件4
所有空节点的孩子都是黑色,不过是空节点,就不需要画出来了。
介绍完红黑树之后,接下来我们来实现一颗红黑树。
💖3. 红黑树的节点创建
首先,我们依旧采用三叉链,一个指针指向左孩子,一个指向右孩子,还有一个指向自己的父亲。再用一个枚举常量col 来记录节点的颜色,kv是存储的一对值。
代码:
enum Color
{
RED,
BLACK
};
//红黑树节点
template<class K,class V>
struct RBTreeNode
{
RBTreeNode(const pair<K,V>& kv)
: _left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_col(RED)
,_kv(kv){
}
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Color _col;
pair<K, V> _kv;
};
💖4.红黑树的定义
红黑树的定义很简单,我们存一个节点指针_root来代表整棵树的根。
//红黑树实现
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
RBTree():_root(nullptr){
}
private:
Node* _root;
};
💖5.节点的插入
个人认为,红黑树的插入并没有AVL那么繁琐。AVL插入时需要考虑的情况太多,而红黑树并不需要考虑太多情况。
假设我们有一颗红黑树

那么接下来我们要插入一个10,那么10应该插入20的左边。那么我们让新插入的节点颜色固定为10。根节点颜色固定为黑。

那么我们发现在插入10之后,这颗红黑树是还是正常的,满足了5条性质。此时我们在插入1个25。

我们发现插入25之后,这棵树还是正常的。所以我们可以得出一个结论,如果插入节点的父亲是黑色,那么这是一颗正常的红黑树。
那么我们此时再插入一个5,它的父亲节点就是10,也就是红色。

这种情况,这颗红黑树就不满足性质3了,因为红节点的孩子必须是黑节点。那么此时我们看它的叔叔,叔叔是25。
那么此时就会出现2种情况
情况1.叔叔存在且为红色
这种情况我们只需要把父亲和叔叔变成黑色,然后把祖父变红,再把cru给祖父,循环操作直到父亲为空即可。

这种操作会把我们的根节点变成红色,所以我们在最后的时候强制把根节点变成黑色即可。
最后这颗红黑树就会变成这样

我们可以发现它依然满足上面的五个性质。
情况2.叔叔不存在或者叔叔为黑色
叔叔不存在或者叔叔为黑色的情况是一样的。
我们先来看叔叔不存在的情况。
叔叔不存在
那么这颗红黑树是这样的

这种情况我们就要发生旋转了,因为叔叔不存在,就意味着这颗红黑树已经左边一边高了。所以我们来个右单旋即可。在右边就左单旋,具体的旋转细节可以看我上篇讲解AVL树的文章。
旋转完之后是这样的

这时候我们在把 c(新增节点) 和 g(祖父节点)变成红色,再把p(父亲节点)变为黑色。这颗红黑树就调整完毕了。

叔叔为黑的情况
叔叔为黑和叔叔不存在的情况是一样的。
我们来看看叔叔为黑的情况。

这时候我们一样要发生旋转。
旋转动图:

然后我们再把c和g变为红色,p变为黑色

这样我们的红黑树就调整完毕了。这是我们的parent在左边,uncle在右边的情况。反过来的逻辑也是一样的。
插入代码:
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
break;
}
//如果cur不为空说明有重复的值
if (cur)
return false;
//创建新节点
cur = new Node(kv);
cur->_parent = parent;
if (cur->_kv.first > parent->_kv.first)
parent->_right = cur;
else
parent->_left = cur;
cur->_col = RED;
//有连续的红节点,需要调整
while (parent && parent->_col == RED)
{
Node* grandparent = parent->_parent;//祖父节点
//parent是左孩子的情况
if (parent == grandparent->_left)
{
// 叔叔节点
Node* uncle = grandparent->_right;
//如果叔叔存在且为红
if (uncle && uncle->_col == RED)
{
//父亲和叔叔变黑
parent->_col = uncle->_col = BLACK

本文深入讲解红黑树的概念、性质及其实现方法,包括节点创建、树定义、节点插入与查找,并提供完整的代码示例。

最低0.47元/天 解锁文章
1104

被折叠的 条评论
为什么被折叠?



