目录
一、红黑树的概念
1、什么是红黑树
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
2、红黑树的性质
1. 每个结点不是红色就是黑色
2. 根节点是黑色的
3. 如果一个节点是红色的,则它的两个孩子结点是黑色的。【不能出现连续的红色节点】
4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,每条路径都包含相同数目的黑色结点
5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
二、红黑树的模拟实现
1、节点的定义
跟AVL树节点差不多,但是需要多定义一个颜色。可以用枚举来定义。
其中,在初始化列表中,默认的节点颜色是红色。是因为当我们新增节点时,如果新增一个黑色节点,那每一条路径都需要更改黑色节点的数量才能满足红黑树的要求,为了减少工作量,所以将默认颜色设置为红色。
2、红黑树的插入操作
红黑树的插入其实就是在搜索树的插入基础上加上了变色处理。所以前面的插入操作和搜索树是一样的。
3、变色+向上处理
我们将新增节点称作 cur,父节点为 parent,父节点的父亲称作 grandfather,grandfather 的另一个子节点称作 uncle。
红黑树的颜色调整的重点在与 uncle 节点,接下来我们也是根据 uncle 节点的情况来分析。
【当uncle 节点存在且为红色时】
我们新增的是一个红色的节点,但是这样就会违反红黑树的规则3(不能出现连续的红节点),所以我们将父节点和叔节点的颜色变成黑色。但是这样又会出现一个新的问题,违反了规则4(每条路径都包含相同数目的黑色结点 )。此时我们将grandfather 的节点颜色变成红节点,这样黑色节点的数量就一样多了。
这时又要分情况讨论:
1.如果祖父没有父亲节点,说明祖父是根节点,根节点必须是黑色节点,所以我们只需要将祖父的颜色变成黑色即可。
2.如果祖父有父亲节点且节点颜色是黑色时,就不用调整了。
3.如果祖父有父亲节点且节点颜色是红色时,就将祖父节点变成 cur,祖父的父亲节点变成 parent,再次重复刚刚的操作即可。
4、变色+旋转
【当 uncle 节点不存在时】
新增的是一个红色节点,此时将父节点的颜色变成红色是不能解决问题的。因为没有uncle节点,然后又新增了一个节点会导致高度差大于1,所以此时我们可以借助AVL数旋转的思想, 来降低子树的高度。将 grandparent 变成 parent 的右节点,将 parent 节点往上移,然后将grandparent节点的颜色变成红色,将 parent 节点的颜色变成黑色。
当左边高就右旋,当右边高就左旋。
【当 uncle 节点为黑色时】
1.当uncle存在且为黑色时,单纯的变色也解决不了问题,也需要使用旋转来处理,处理完后再进行变色。
2.不过这种情况一般都是从下面变色上来遇到的,所以可能是不断的往上变色处理然后遇到了这种情况,无法处理就需要使用旋转了。而旋转是通过图形来确定的。
我们会发现 uncle 不存在或者uncle存在且为黑色时,需要变色+旋转,所以在写代码时我们可以将他合成一种。 在哪边插入,哪边的高度就会上升,我们使用旋转使高度下降。
因为一开始不知道叔叔是在那边,叔叔在哪侧位置会决定旋转的方向(本质上是父亲在哪边决定旋转方向),所以需要讨论一下,当父亲在左边,那么叔叔就在右边。父亲在左边那么进行的的就是右旋和双旋,如果父亲在右边,那么进行的就是左旋和双旋
【总结】
红黑树节点的变色与旋转的关键在于 uncle 节点
当uncle存在且为黑色时,需要进行旋转+变色处理。
当uncle不存在时,需要进行旋转+变色处理。
当uncle存在且为红色时,需要进行变色+向上处理。
5、红黑树的查找
从根开始查找,如果大于就往右边找;如果小于就往左边找;等于就找到了。如果没有找到直接返回 false。
6、红黑树的析构
如果这棵树是空树,直接返回。然后用后序,递归依次销毁。
三、红黑树的验证
红黑树的检测分为两步:
1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)
2. 检测其是否满足红黑树的性质
1、是否满足搜索二叉树
2、是否满足红黑树的性质
检验是否为红黑树,我们按照红黑树的性质一条一条的来检查:
解决思路:
1、如果根节点存在且为红色,那就不是红黑树
2、不能有连续的红节点----如果是红节点,那么它的父节点就不能是红节点。
3、可以算出一条路径上黑色节点的数量,然后跟其他路径的黑色节点数量相比较,如果出现不相等就不符合红黑树。
【测试代码】
四、红黑树和AVL数的比较
红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(log_2 N),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数, 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。
五、完整代码
#pragma once
//用枚举来定义每一个节点的颜色
enum Colour
{
RED,
BLACK,
};
template<class K, class V>
struct RBTreeNode
{
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
pair<K, V> _kv;
Colour _col;
RBTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
void _Destroy(Node* root)
{
if (root == nullptr)
{
return;
}
_Destroy(root->_left);
_Destroy(root->_right);
delete root;
}
~RBTree()
{
_Destroy(_root);
_root = nullptr;
}
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->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//接下来就是颜色的调整
while (parent && parent->_col == RED)
{
Node* grandparent = parent->_parent;
if (grandparent->_left == parent)
{
Node* uncle = grandparent->_right;
//情况一:u存在且为红
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
//继续往上调整
cur = grandparent;
parent = cur->_parent;
}
else//u不存在/u存在且为空
{
//往左边插入,左边的高度会变高
// g
// p u
// c
if (cur == parent->_left)
{
RotateR(grandparent);
parent->_col = BLACK;
grandparent->_col = RED;
}
else
{
//往右边插入
// g
// p u
// c
RotateL(parent);
RotateR(grandparent);
cur->_col = BLACK;
parent->_col = RED;
grandparent->_col = RED;
}
break;
}
}
else//grandparent->right==parent
{
// g
// u p
// c
Node* uncle = grandparent->_left;
//
if (uncle && uncle->_col == RED)
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandparent->_col = RED;
// 继续往上调整
cur = grandparent;
parent = cur->_parent;
}
else // u不存在/u存在且为黑,旋转+变色
{
// g
// u p
// c
if (cur == parent->_right)
{
RotateL(grandparent);
grandparent->_col = RED;
parent->_col = BLACK;
}
else
{
// g
// u p
// c
RotateR(parent);
RotateL(grandparent);
cur->_col = BLACK;
grandparent->_col = RED;
}
break;
}
}
}
//不管怎样根节点都要保持黑色
_root->_col = BLACK;
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
//判断是否是红黑树
bool IsBalance()
{
if (_root && _root->_col == RED)
{
cout << "根节点颜色是红色" << endl;
return false;
}
int benchmark = 0;
//计算某一条路径的黑色节点
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
++benchmark;
cur = cur->_left;
}
// 连续红色节点
return _Check(_root, 0, benchmark);
}
bool _Check(Node* root, int blackNum, int benchmark)
{
if (root == nullptr)
{
if (benchmark != blackNum)
{
cout << "某条路径黑色节点的数量不相等" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++blackNum;
}
//判断是否有连续的红色节点
if (root->_col == RED
&& root->_parent
&& root->_parent->_col == RED)
{
cout << "存在连续的红色节点" << endl;
return false;
}
return _Check(root->_left, blackNum, benchmark)
&& _Check(root->_right, blackNum, benchmark);
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppnode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (ppnode == nullptr)
{
_root = subR;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subR;
}
else
{
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppnode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subL;
}
else
{
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
}
private:
Node* _root = nullptr;
};
void Test_RBTree1()
{
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
RBTree<int, int> t1;
for (auto e : a)
{
t1.Insert(make_pair(e, e));
cout << e << "插入:" << t1.IsBalance() << endl;
}
t1.InOrder();
}