[!NOTE]- 前言
最近学习STL花费的时间实在太多,导致了我本来的学习计划有些被搁置。我的一个朋友也告诉我,说像红黑树这样的东西都是没什么必要的。因为一周不复习就遗忘了,而且无论是面试还是工程里面都基本不会考这个或者用这个。大家调侃的面试手撕红黑树也只是调侃而已,不会真有面试这样去做。我认为他说的其实有道理,我确实在STL上花费了很多时间,而恰恰我现在的时间又十分宝贵,因为要找实习,准备秋招。但是我想的是既然STL都已经做到这里了,就善始善终吧,再坚持一下,把这些完成了,再去做项目。其实这些东西做一遍也是很有好处的,至少对这些数据结构的认知就更加清楚了。至于到底有没有用,其实大多东西都是无用的,但学习的过程主要是培养编程的思维和习惯,所以再坚持一下,把STL做完,就考虑开始做6.824了。
红黑树
提起红黑树,我对它的印象就是:红黑树非常地难,但效率很高。但是也没用深入地去了解过,包括之前对于红黑树的认知停留在网上的梗,说现在公司都要求手撕红黑树。总之这都是停留在红黑树很复杂这么一个概念。红黑树我没找到很好的视频,所以就靠看博客看书学习了。
两条性质
红黑树有两条基础性质:
- 所有根节点到叶子节点的路径的黑节点数量都相同
- 不会出现两个相邻的红节点
当然,还有其他基础性质,比如叶子节点和根节点必须是黑节点啊,叶子节点是空节点不存放数据之类的。都是一些小性质,为了降低对红黑树的理解门槛,就不提了,学习的时候再注意即可。真正要关注的其实就是上面两条基础性质。
插入
红黑树的插入不需要AVL树那么频繁的左旋右旋操作,可以分为以下4中情况(默认插入节点为红色)
- 如果父节点为黑;直接插入
- 如果父节点为红,叔节点为红;将父叔节点颜色与爷爷节点互换
- 如果父节点为红,叔叔节点为黑,且父子都是同一个方向的节点(比如父节点为右孩子,子节点也为右孩子);将父节点爷爷节点颜色互换,随后爷爷节点右旋(针对前面的比如而言)
- 若父节点为红,叔叔节点为黑,且父子是不同方向的节点(比如父节点为右孩子,子节点为左孩子);将父节点右旋(针对前面的比如),然后继续按照第3点来处理
删除
…
代码
节点定义
enum Color
{
BLACK,
RED
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _parent;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
pair<K, V> _kv;
Color _col;
RBTreeNode(const pair<K,V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_col(RED)
{}
};
可以看到,相比于AVL树,红黑树节点的定义唯一的变化是平衡因子变成了表达红色黑色性质的两个节点RED,BLACK,且默认节点为红色。
插入节点
红黑树插入节点也可以拆分为2步
- 按二叉树搜索树的方式插入节点
- 调整树的颜色
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root->_kv = kv;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first < kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
// 处理颜色
return true;
}
处理颜色
处理颜色是整个红黑树的精髓,非常建议大家先理解234树,因为红黑树其实本质上与4阶B树是等价的。这里推荐一篇文章,是CSDN上的
红黑树详解
while (parent && parent->_col == RED)
{
Node* grandfather = parent->_parent;
// 叔叔节点存在
if (parent == grandfather->_left)
{
Node* uncle = grandfather->_right;
// LL or RR的上溢情况
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
// 递归上溢
cur = grandfather;
parent = cur->_parent;
}
else//叔父节点不存在
{
if (cur = parent->_left)//LL,右旋,参考AVL树
{
RorateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
else//LR, cur变成grandfather,father变成left,grandfather变成right
{
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;//234树只有2个节点,不可能存在上滤情况,break;
}
}
else // parent = grandfather.right
{
Node* uncle = grandfater->_left;
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandfater->_col = RED;
cur = grandfater;
parent = cur->_parent;
}
else
{
if (cur == parent->_right)
{
RotateL(grandfater);
parent->_col = BLACK;
grandfater->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandfater);
cur->_col = BLACK;
grandfater->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
结束语
红黑树就到这里了,其实删除操作更复杂,没有时间去实现了,等我找到工作了或者有空余时间一定回来把删除节点更新完~