小伙伴们大家好,本片文章将会讲解
平衡二叉搜索树
之红黑树
的相关内容。
如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!
1. 何为红黑树
🧐红黑树的定义
红黑树,是一种二叉搜索树,它在每个结点上增加一个存储位表示结点的颜色,可以是 Red 或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
它在插入和删除节点时能够保持树的平衡,从而保证了搜索、插入和删除等操作的时间复杂度为 O ( l o g n ) O(log n) O(logn)。
🧐红黑树的性质
1、 每个节点不是红色就是黑色;
2、 根节点的颜色是黑色的;
3、不能出现连续的红色节点;
4、每条路径上拥有相同数量的黑色节点;
5、每个叶子节点的颜色是黑色的(这里的叶子节点指的是空节点)。
问题来了,为什么满足以上条件就能保证最长路径不会超过最短路径的两倍呢?
主要看
3、4
两个条件,那么最短路径肯定就是连续的黑色节点,最长路径就是一黑一红交替出现,假设黑色节点的数量是n
,最短路径的长度就是n
,最长路径的长度就是2n
。(最短和最长可能在红黑树中不会出现)
2. 关于红黑树的插入
🧐红黑树插入节点的初始颜色
我们插入节点一定也要满足红黑树的5个条件,尤其是第三条和第四条,即:
- 每条简单路径上的黑色节点数量相同;
- 不能出现连续的红色节点。
- 即一个红色节点的孩子节点只能是黑色节点。
那么如果我们: 插入的是 黑色 节点,就会导致此条路径上的黑色节点数量增加,但是其他路径上的黑色节点数量没有变化,非常难以调节。
但是如果我们: 插入的是 红色 节点,只需要判断一下它的父亲节点是否是红色 ,如果是红色,就继续向上调节,如果不是红色(黑色),说明不需要调节,直接插入即可。
🧐红黑树插入节点时的调节
据上所述,插入的节点是红色节点,只有当父亲也是红色节点的时候才要进行调整,那么调整分为以下几种情况(主要看叔叔):
首先约定当前节点为
cur
,父亲节点为parent
,祖父节点为grandparent
,叔叔节点为uncle
🏄🏼♂️情况1:叔叔存在且为红色
1、uncle在grandparent的右边,cur在parent的左边:
我们调节的时候一定要保证满足红黑树的条件, 一定不要改变各个路径中的黑色节点的个数。
由于 不能出现两个连续的红色节点,所以我们要把:
1、parent
节点的颜色改为 黑色(导致父亲所在路径黑色数量增加);
2、把grandparent
节点的颜色变为 红色(解决1的问题,引发uncle
节点所在路径黑色节点数目减少);
3、把uncle
节点颜色 变红(解决2的问题)。
4、由于根节点变为红色,所以还要继续向上调整,因为grandparent
的parent
节点可能也是红色。
5、如果调节到根节点,把根节点变为黑色。
这样调整咱们就满足了红黑树的条件,图解如下:
除此之外,可能还有以下几种情况,但解决方法都相同:
2、uncle在grandparent的右边,cur在parent的右边:
3、 uncle在grandparent的左边,cur在parent的右边:
4、uncle在grandparent的左边,cur在parent的左边:
🏄🏼♂️情况2:叔叔不存在或者存在为黑色
1、 uncle在grandparent的左边,cur在parent的左边:
我们调节的时候一定要保证满足红黑树的条件, 一定不要改变各个路径中的黑色节点的个数。
由于 不能出现两个连续的红色节点,所以我们要把:
1、parent
节点的颜色改为 黑色(导致父亲所在路径黑色数量增加);
2、把grandparent
节点的颜色变为 红色(解决1的问题,引发uncle
节点所在路径黑色节点数目减少);
3、将grandparent
节点进行一次右旋转(上图情况为右旋转);
除此之外,可能还有以下几种情况,但解决方法类似(旋转方向、次数不同):
2、 uncle在grandparent的右边,cur在parent的右边:
此时就不是单旋那么简单了,我们要把这种情况首先变成上面的那种情况,所以首先要对parent
进行左单旋,单旋之后cur
变成了上面情况parent
所在的位置,因此要把cur
的颜色变为黑色,之后的操作就和上面的情况一样了。
先对parent
进行左单旋,再对grandparent
进行右单旋
3、uncle在grandparent的左边,cur在parent的左边:
变色之后,旋转时:先对parent
进行右单旋,再对grandparent
进行左单旋
4、 uncle在grandparent的左边,cur在parent的右边:
这种情况变色之后直接对grandparent
左单旋。
3. C++模拟实现红黑树
🏄🏼♂️节点类型定义
// 枚举类型定义颜色
enum Colour
{
RED,
BLACK
};
template<class K, class V>
struct RBTNode
{
typedef RBTNode<K, V> Node;
Node* _left;
Node* _right;
Node* _parent;
pair<K, V> _kv;
Colour _col;
RBTNode(const pair<K, V>& kv)
:_kv(kv)
,_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_col(RED)// 初始给红色,上面讲过
{}
};
🏄🏼♂️插入的模拟实现
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 (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandparent = parent->_parent;
// 父亲在祖父的左边,祖父的右边是叔叔
if (parent == grandparent->_left)
{
Node* uncle = grandparent->_right;
// 叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
// 继续向上判断
cur = grandparent;
parent = cur->_parent;
}
// 叔叔不存在 或者 存在且为黑
else
{
// cur 在父亲左边
if (cur == parent->_left)
{
// g(B) g(R)
// p(R) u(B) => p(B) u(B)
// c(R) c(R)
parent->_col = BLACK;
grandparent->_col = RED;
RotateR(grandparent);
}
// cur 在父亲右边
else
{
// g(B) g(B)
// p(R) u(B) => c(R) u(B)
// c(R) p(R)
cur->_col = BLACK;
grandparent->_col = RED;
RotateL(parent);
RotateR(grandparent);
}
break;
}
}
// 父亲在祖父的右边,祖父的左边是叔叔
else
{
Node* uncle = grandparent->_left;
// 叔叔存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = uncle->_col = BLACK;
grandparent->_col = RED;
cur = grandparent;
parent = cur->_parent;
}
// 叔叔不存在,或者存在且为黑
else
{
// cur 在父亲右边
if (cur == parent->_right)
{
// g(B) g(R) p(B)
// u(B) p(R) u(B) p(B) g(R) cur(R)
// cur(R) cur(R) u(B)
parent->_col = BLACK;
grandparent->_col = RED;
RotateL(grandparent);
}
// cur 在父亲左边
else
{
// g(B) g(B) cur(B)
// u(B) p(R) u(B) cur(R) g(R) cur(R)
// cur(R) p(R) u(B)
cur->_col = BLACK;
grandparent->_col = RED;
RotateR(parent);
RotateL(grandparent);
}
break;
}
}
}
// 如果更新到根节点,更新为黑色
_root->_col = BLACK;
return true;
}
🏄🏼♂️左单旋和右单旋
这里有问题的可以看以下博主的上一篇关于AVL树
的文章,里面有详细的讲解
void RotateR(Node* root)
{
Node* subL = root->_left;
Node* subLR = subL->_right;
root->_left = subLR;
if (subLR)
subLR->_parent = root;
subL->_right = root;
Node* ppNode = root->_parent;
root->_parent = subL;
if (ppNode)
{
if (ppNode->_left == root)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
else
{
_root = subL;
subL->_parent = nullptr;
}
}
void RotateL(Node* root)
{
Node* subR = root->_right;
Node* subRL = subR->_left;
root->_right = subRL;
if (subRL)
subRL->_parent = root;
subR->_left = root;
Node* ppNode = root->_parent;
root->_parent = subR;
if (ppNode)
{
if (ppNode->_left == root)
{
ppNode->_left = subR;
}
else
{
ppNode->_right = subR;
}
subR->_parent = ppNode;
}
else
{
_root = subR;
subR->_parent = nullptr;
}
}
🏄🏼♂️测试
void RBTest()
{
//int a[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,16,
3, 7, 11, 9, 26, 18, 14, 15 };
RBTree<int, int> t;
for (auto& e : a)
{
t.Insert({ e,e });
}
t.InOrder();
}
4. 检测函数
检查的时候无非就是看一下是否满足红黑树的几个条件,最重要的就是三、四两点,思路如下:
1、看一下根节点是否为红色(条件2
的检查);
2、对于一个节点如果是红色,看一下它的父亲节点是否为红色(条件3
的检查);
最难的是
条件4
,如何检查每条路径上的黑色节点数量相同呢?
可以采用 深度优先(DFS
) 的思路:
1、先算一条路径的黑色节点数(比如最左路径);
2、然后把这个值传入到Check
函数;
3、每当指针走到空的时候都判断以下和这个参考值是否相同,如果不同说明有错。
public:
bool IsBalance()
{
// 判断根节点颜色
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);
}
private:
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->_col == RED)
{
cout << root->_kv.first << "存在连续的红色节点" << endl;
return false;
}
// 如果为黑色,数量加1
if (root->_col == BLACK)
{
blackNum++;
}
// 递归调用
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
5. 完整代码
🎉博主gitee链接: 红黑树完整代码
有需要的小伙伴自取哈: