小编在学习完红黑树之后,发现红黑树的实现相对于AVL树来说会简单一点,并且大家在学了C++中的set和map容器之后,会明白set和map的容器的底层就是运用的红黑树,因为相对于AVL树,红黑树的旋转次数会大大减少,并且也会保持树的相对的平衡,相对于搜索二叉树,也会大大的提高查找效率,所以在此请大家跟着小编一起学习今天的内容红黑树!~~~~
在此学习之前大家应该先要明白红黑树的底层原理和红黑树的构造
一、红黑树的底层原理
先附上一张图让大家知道红黑树的构造:
上面大家就可以看到红黑树的每个节点都是一红一黑这样排序,并且红色节点不可以连续出现超过两次,好滴,了解到这块就可以告诉大家红黑树的底层原理了,为面了解红黑树和实现红黑树做铺垫。
底层原理:
这个思考题大家可以想一想为什么红黑可以保证这样的特性,我在下面给大家讲解:
二、红黑树的实现原理
对于红黑树的实现过程,相对于搜索二叉树,不同也就是插入过程的调整,让红黑树一直满足它的底层逻辑,不能破坏红黑树的底层结构,让它一直维持在最长路径不会超过最短路径的二倍,让它保持平衡,那实现红黑树我们就要新增一个节点的颜色和父节点的指针,新增节点的颜色就是为了让它红色的节点不要连续出现,父节点的指针也是为了后续的调整做准备(后面会讲),那有了这些我们该如何实现一颗红黑树呢?在这里我直接告诉大家:
实现:每次插入一个红色节点,但是保证根节点是黑色的,如果插入的过程中,遇到了连续的红色节点,因为不满足红黑树的条件,所以我们要对此进行调整(后面会讲如何调整),调整的过程中需要保证每条路径的黑色节点个数相同并且把连续的红色节点调整开来,这样就可以达到调整的目的,并且保持红黑树的特性。
三、红黑树的实现
1、大家先来实现一下这个红黑树的基础结构,在红黑树的实现过程中相对于搜索二叉树我们加入了节点的颜色和父节点的指针,代码如下:
enum Color // 记录红黑树节点颜色的枚举类型
{
RED,
BLACK
};
template<class T1, class T2>
struct RBTreeNode
{
pair<T1, T2> _data;
struct RBTreeNode<T1, T2>* _left;
struct RBTreeNode<T1, T2>* _right;
struct RBTreeNode<T1, T2>* _parent; // 存的是当前节点的父节点
int col;
RBTreeNode(const pair<T1, T2>& data)
:_data(data)
, _left(nullptr)
, _right(nullptr)
, col(RED)
, _parent(nullptr)
{}
};
2、接下来我们来实现一下红黑树的插入过程,也是最重要的过程,在不断插入数据的同时我们需要做到每次插入的节点应该是合法的,如果不合法的话,就需要我们做到调整来保证这个红黑树的特性不变。那该如何调整呢,这里有三种情况需要我们逐个分析,我把三种情况都放到下面供大家参考和学习:
在开始学习时先告诉大家这里调整会用到的小知识:
a、uncle节点存在并且为红色节点
b、uncle节点存在并且为黑色 或者 uncle节点不存在
调整完之后如果不符合红黑树的特点,仍然向上继续调整,不过此时cur节点变为parent节点
c、需要进行双旋的操作,并且黑色节点存在并且为黑 或者 黑色节点不存在
调整完之后如果不符合红黑树的特点,仍然向上继续调整,不过此时cur节点变为parent节点
好啦以上就是插入过程中的调整过程,让每次插入之后这颗树仍然符合红黑树的底层逻辑,那接下来大家就知道如何实现插入部分的代码了吧,我把代码和注释放到下面供大家参考和对照:
注意:每次调整完之后如果上面节点不满足红黑树的特性,仍然需要往上继续调整
提醒:这里的旋转过程和AVL树的旋转方法一样,如果大家不明白请看我上篇博客,里面详细讲解了如何单旋和双旋的操作。
bool Insert(const pair<T1, T2>& x)
{
if (_root == nullptr) // 第一次插入节点 根节点是黑色的
{
_root = new Node(x);
_root->col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data.first < x.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data.first > x.first)
{
parent = cur;
cur = cur->_left;
}
else
{
// 走到else说明搜索二叉树里面有这个值,所以就不用插入
return false;
}
}
if (parent->_left == cur && parent->_data.first > x.first) // 确定插入的值在哪边
{
parent->_left = new Node(x);
parent->_left->_parent = parent;
cur = parent->_left;
}
else
{
parent->_right = new Node(x);
parent->_right->_parent = parent;
cur = parent->_right;
}
while (parent && parent->col == RED) // 如果插入的值的父节点是红色 说明就需要调整
{ // 并且此时肯定存在祖父节点 因为根节点必须为红色
Node* grandfather = parent->_parent; // 通过祖父节点确定叔叔的位置
Node* uncle = nullptr;
if (grandfather->_left == parent)
uncle = grandfather->_right;
else
uncle = grandfather->_left;
if (uncle && uncle->col == RED) // 情况一 叔叔存在并且为红色
{
parent->col = uncle->col = BLACK;
grandfather->col = RED;
// 节点继续向上更新
cur = grandfather;
parent = cur->_parent;
}
else if ((uncle && uncle->col == BLACK) || uncle == nullptr) // 情况二 叔叔存在且为黑色 或者叔叔不存在
{
if (grandfather->_left == parent && parent->_left == cur)
{
RotateR(grandfather);
parent->col = BLACK;
grandfather->col = RED;
break;
}
else if (grandfather->_right == parent && parent->_right == cur)
{
RotateL(grandfather);
parent->col = BLACK;
grandfather->col = RED;
break;
}
else if (grandfather->_left == parent && parent->_right == cur)
{
RotateL(parent);
parent = grandfather->_left;
cur = parent->_left;
}
else if (grandfather->_right == parent && parent->_left == cur)
{
RotateR(parent);
parent = grandfather->_right;
cur = parent->_right;
}
else
assert(false);
}
else
assert(false);
_root->col = BLACK;
}
return true;
}
好啦,上面就是红黑树中最难的插入节点的部分,调整部分相信大家看完一定会收获满满,如果哪里还不明白可以私信我或者评论区留言。
3、接下来就是红黑树的销毁和遍历部分,这部分和搜索二叉树一样,我就不在这里做详细讲解了,大家如果有什么不理解的可以评论区留言或者私信我,我帮大家解答,代码及注释我就放到下面了,供大家参考:
// 查找二叉树的中序遍历刚好是顺序排序
void InOrder()
{
// 中序遍历需要递归来解决,所以这个 _root 不好传 如果直接用_root 的话 _root 就会被改变
Node* cur = _root;
_InOrder(cur);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_data.first << ":" << root->_data.second << endl;
_InOrder(root->_right);
}
~RBTree()
{
// 后序删除空间 左 右 根
_RBTreeDestory(_root);
_root = nullptr;
}
void _RBTreeDestory(Node* root)
{
if (root == nullptr)
return;
_RBTreeDestory(root->_left);
_RBTreeDestory(root->_right);
delete root;
}
4、为了验证自己写的红黑树是否满足它的特性,我在这里已经写好了验证方式的代码,我把它传到下面,大家写完自己的红黑树可以用下面的代码测试一下是否为一颗满足条件的红黑树,代码及注释:
bool IsRBTree()
{
Node* root = _root;
if (_root->col == RED) // 性质一:根节点是黑色
return false;
// 性质二:每条路径上的黑色节点数相同
int BKNum = 0;
while (root) // 先计算一条路径上的黑色节点
{
if (root->col == BLACK)
BKNum++;
root = root->_left;
}
// 性质三:红色节点不能连续出现
return __IsRBTree(_root, BKNum);
}
bool __IsRBTree(Node* root, int k)
{
// 前序遍历判断黑色元素个数
if (root == nullptr)
return k == 0;
if (root->col == BLACK)
k--;
if (root->col == RED) // 如果此节点是红色,那他的父节点就不能为红色
{
if (root->_parent->col == RED)
return false;
}
__IsRBTree(root->_left,k);
__IsRBTree(root->_right,k);
}
好啦,以上就是今天的红黑树的所有内容,相信大家一定会有所收获,如果还有疑惑的话,评论区可以留言或者私信我,我来帮大家节点,好啦,下期再见各位!~~~