红黑树的提出:
之前我们介绍了二叉搜索树, 理想情况下二叉搜索树的性能不错, 但是在极端情况下(数据有序, 或者接近有序)二叉搜索树会退化为单支树, 从而导致其操作效率很低, 时间复杂度为O(N), 于是俄罗斯数学家提出了平衡二叉搜索树,又称为AVL树. AVL树引入了平衡因子,保证了树的平衡, 从而避免了二叉搜索树会退化为单支树的情况, 进而使得AVL操作时间复杂度为log(N), 但是AVL的应用却比较少, 因为AVL的维护,成本太高,对AVL树进行插入或者删除时, 需要不停地通过左旋,右旋操作来维持其平衡,这些操作的成本太高,甚至抵消了AVL树带来的性能提升.
还有另外一种平衡树2-3树,红黑树也借鉴了其思想.2-3树就不介绍了, 感兴趣的同学请查看2-3树
红黑树同时借鉴了AVL树和2-3树的思想, 将AVL的完美平衡改进为局部平衡, 通过改变颜色来区分不同的结点类型, 从而降低了维护平衡的成本和实现的复杂度.
红黑树的概念:
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
特点:
- 每个结点不是黑色就是红色
- 根节点是黑色的
- 如果一个节点是红色的, 则它的两个孩子都是黑色的
- 对于每一个结点, 从该结点到其所有后代叶结点的路径上, 均包含相同数目的黑色结点
- 每一个叶子结点都是黑色的(注意:此处所指的叶子结点指的是真正的叶子结点所连接的空节点)
红黑树如何保证其最长路径结点个数不会超过最短路径结点个数的两倍?
- 根据红黑树的性质, 最短路径理想情况下就是全为黑色结点, 最长路径理想情况就是红黑结点相间,从而保证了最长路径结点数不会超过最短路径结点数目的两倍.
红黑树实现详解:
红黑树结点定义:
// 结点的颜色
enum Colour
{
red,
black
};
// 结点的定义
template<class T>
struct RBTreeNode
{
RBTreeNode<T> *_left; // 结点的左孩子
RBTreeNode<T> *_right; // 结点的右孩子
RBTreeNode<T> *_parent; // 结点的父结点
T _data; // 结点的值域
Colour _col; // 结点的颜色
RBTreeNode(const T& data) :
_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(red)
{}
};
红黑树的插入操作:
红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:
- 按照二叉搜索的树规则插入新节点
- 检测新节点插入后,红黑树的性质是否造到破坏
因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论:
约定:cur为当前节点,p为父节点,g为祖父节点,u为叔叔节点
- 情况一:p为红色结点, g为黑色结点, u存在且为红色结点
- 情况二: cur为红色结点, p为红, g为黑, u不存在或者为黑
- 情况三: cur为红, p为红, g为黑, u不存在或者为黑
针对每一种情况进行相应的处理.
bool Insert(const T &val)
{
// 如果根节点为 null
if (_root == nullptr)
{
_root = new Node(val);
_root->_col = black;
return true;
}
// 根结点不为 null
else
{
/************************************************************************/
/* 按照二叉搜索树的方式插入新结点 */
/************************************************************************/
Node *cur = _root;
Node *parent = _root;
while (cur)
{
if (cur->_data < val)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > val)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
// 此时, cur结点应为null, 处于要插入的位置
Node *newNode = new Node(val);
cur = newNode;
cur->_parent = parent;
if (cur->_data < parent->_data)
parent->_left = cur;
else
parent->_right = cur;
/* 检测新结点插入后, 红黑树的性质是否被破坏
未破坏红黑树性质, 则直接退出, 否则进行旋转,着色处理
*/
// 如果新插入结点的父结点是黑色结点, 红黑树的性质未被破坏可以直接退出
if (parent->_col == black)
return true;
/* 新插入结点默认颜色为红色, 如果其父结点也为红色, 则会破坏红黑树的性质---不能有连续的红结点
此时会有多种情况可能发生, 此处我们约定:
cur 表示当前结点(红色)
parent 表示 cur 的父结点
gparent 表示 parent 的父结点, 即 cur 的祖父结点
uncle 表示 parent 的兄弟结点, 即 cur 的叔叔结点
*/
while (parent != nullptr && parent->_col == red)
{
Node *gparent = parent->_parent; // 因为parent存在, 且不是黑色结点, 则parent不是根, 所以 parent 肯定存在父结点 gparent
Node *uncle = nullptr;
if (parent == gparent->_left)
{
uncle = gparent->_right;
} else
{
uncle = gparent->_left;
}
// 情况一: parent 为红色结点, gparent 为黑色结点, uncle结点存在且为红色结点
if (uncle != nullptr && uncle->_col == red)
{
// 将 parent 与 uncle 结点变黑, gparent 变红, 因为gparent变红可能会与它的根结点颜色发生冲突, 所以继续向上调整
parent->_col = black;
uncle->_col = black;
gparent->_col = red;
// 继续向上调整
cur = gparent;
parent = cur->_parent;
}
// 情况二: cur 为红结点, parent 为红结点, gparent 为黑结点, uncle结点为黑色结点, 或者不存在
// 情况三: cur 为红结点, parent 为红结点, gparent 为黑结点, uncle结点为黑色结点, 或者不存在
else
{
// 如果parent是 gparent 的左孩子结点, 则对 gparent 进行 右单旋----
// 如果parent是 gparent 的右孩子结点, 则对 gparent 进行 左单旋----
// 最后将parent 变为黑色, gparent 变为红色
if (parent == gparent->_left)
{
// 情况三, 操作完成后变成操作二的情况
if (cur == parent->_right)
{
RotateL(parent);
swap(cur, parent); // 变成了情况二的情况后, 重新标记 cur parent 位置使之与情况二一样
}
parent->_col = black;
gparent->_col = red;
RotateR(gparent);
break;
}
else
{
if (cur == parent->_left)
{
RotateR(parent);
swap(cur, parent);
}
parent->_col = black;
gparent->_col = red;
RotateL(gparent);
break;
}
}
}
}
_root->_col = black;
return true;
}
红黑树的验证:
怎么来验证我们实现的是一颗符合规则的红黑树呢?
我们借助其性质来完成检验
- 检测其是否满足二叉搜索树的性质(中序遍历为有序序列)
- 检测其是否满足红黑树的性质
public:
bool IsRBTree()
{
if (_root == nullptr)
return true;
if (_root->_col != black)
{
cout << "根节点不为黑色" << endl;
return false;
}
// 统计出一条路径上黑色结点的数量
size_t blackcount = 0;
Node *cur = _root;
while (cur != nullptr)
{
if (black == cur->_col)
blackcount++;
cur = cur->_left;
}
size_t k = 0;
return _IsRBTree(_root, k, blackcount);
}
private:
bool _IsRBTree(RBTreeNode<T> *root, size_t k, const size_t blackcount)
{
if (root == nullptr)
{
if (k != blackcount)
{
cout << "不满足每条路径中的黑色结点数目相同" << endl;
return false;
}
return true;
}
// 统计当前路径黑色结点数量
if (root->_col == black)
k++;
// 检测是否有连续的红色结点出现
Node *parent = root->_parent;
if (parent != nullptr && parent->_col == red && root->_col == red)
{
cout << "出现连续的红色结点" << endl;
return false;
}
return _IsRBTree(root->_left, k, blackcount) && _IsRBTree(root->_right, k, blackcount);
}
完整实现:
#pragma once
#include <iostream>
using namespace std;
enum Colour
{
red,
black
};
template<class T>
struct RBTreeNode
{
RBTreeNode<T> *_left;
RBTreeNode<T> *_right;
RBTreeNode<T> *_parent;
T _data;
Colour _col;
RBTreeNode(const T& data) :
_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _col(red)
{}
};
template<class T>
class RBTree
{
typedef RBTreeNode<T> Node;
private:
Node *_root;
Node *_Nil;
public:
RBTree()
:_root(nullptr)
{}
~RBTree()
{
//Destory();
}
RBTree(const RBTree& t)
{
return t;
}
void PreOrder()
{
_PreOrder(_root);
}
void Inorder()
{
_Inorder(_root);
}
void PostOrder()
{
_PostOrder(_root);
}
bool Insert(const T &val)
{
// 如果根节点为 null
if (_root == nullptr)
{
_root = new Node(val);
_root->_col = black;
return true;
}
// 根结点不为 null
else
{
/************************************************************************/
/* 按照二叉搜索树的方式插入新结点 */
/************************************************************************/
Node *cur = _root;
Node *parent = _root;
while (cur)
{
if (cur->_data < val)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_data > val)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
// 此时, cur结点应为null, 处于要插入的位置
Node *newNode = new Node(val);
cur = newNode;
cur->_parent = parent;
if (cur->_data < parent->_data)
parent->_left = cur;
else
parent->_right = cur;
/* 检测新结点插入后, 红黑树的性质是否被破坏
未破坏红黑树性质, 则直接退出, 否则进行旋转,着色处理
*/
// 如果新插入结点的父结点是黑色结点, 红黑树的性质未被破坏可以直接退出
if (parent->_col == black)
return true;
/* 新插入结点默认颜色为红色, 如果其父结点也为红色, 则会破坏红黑树的性质---不能有连续的红结点
此时会有多种情况可能发生, 此处我们约定:
cur 表示当前结点(红色)
parent 表示 cur 的父结点
gparent 表示 parent 的父结点, 即 cur 的祖父结点
uncle 表示 parent 的兄弟结点, 即 cur 的叔叔结点
*/
while (parent != nullptr && parent->_col == red)
{
Node *gparent = parent->_parent; // 因为parent存在, 且不是黑色结点, 则parent不是根, 所以 parent 肯定存在父结点 gparent
Node *uncle = nullptr;
if (parent == gparent->_left)
{
uncle = gparent->_right;
} else
{
uncle = gparent->_left;
}
// 情况一: parent 为红色结点, gparent 为黑色结点, uncle结点存在且为红色结点
if (uncle != nullptr && uncle->_col == red)
{
// 将 parent 与 uncle 结点变黑, gparent 变红, 因为gparent变红可能会与它的根结点颜色发生冲突, 所以继续向上调整
parent->_col = black;
uncle->_col = black;
gparent->_col = red;
// 继续向上调整
cur = gparent;
parent = cur->_parent;
}
// 情况二: cur 为红结点, parent 为红结点, gparent 为黑结点, uncle结点为黑色结点, 或者不存在
// 情况三: cur 为红结点, parent 为红结点, gparent 为黑结点, uncle结点为黑色结点, 或者不存在
else
{
// 如果parent是 gparent 的左孩子结点, 则对 gparent 进行 右单旋----
// 如果parent是 gparent 的右孩子结点, 则对 gparent 进行 左单旋----
// 最后将parent 变为黑色, gparent 变为红色
if (parent == gparent->_left)
{
// 情况三, 操作完成后变成操作二的情况
if (cur == parent->_right)
{
RotateL(parent);
swap(cur, parent); // 变成了情况二的情况后, 重新标记 cur parent 位置使之与情况二一样
}
parent->_col = black;
gparent->_col = red;
RotateR(gparent);
break;
}
else
{
if (cur == parent->_left)
{
RotateR(parent);
swap(cur, parent);
}
parent->_col = black;
gparent->_col = red;
RotateL(gparent);
break;
}
}
}
}
_root->_col = black;
return true;
}
bool IsRBTree()
{
if (_root == nullptr)
return true;
if (_root->_col != black)
{
cout << "根节点不为黑色" << endl;
return false;
}
// 统计出一条路径上黑色结点的数量
size_t blackcount = 0;
Node *cur = _root;
while (cur != nullptr)
{
if (black == cur->_col)
blackcount++;
cur = cur->_left;
}
size_t k = 0;
return _IsRBTree(_root, k, blackcount);
}
private:
// 销毁红黑树
void Destory()
{
if (_root == nullptr)
return;
if (_root->_left != nullptr)
return Destory(_root->_left);
if (_root->_right != nullptr)
return Destory(_root->_right);
delete _root;
_root = nullptr;
}
// 前序遍历红黑树
void _PreOrder(RBTreeNode<T> *root)
{
if (root != nullptr)
{
cout << root->_data << ' ';
_PreOrder(root->_left);
_PreOrder(root->_right);
}
}
// 中序遍历红黑树
void _Inorder(RBTreeNode<T> *root)
{
if (root != nullptr)
{
_Inorder(root->_left);
cout << root->_data << ' ';
_Inorder(root->_right);
}
}
// 后序遍历红黑树
void _PostOrder(RBTreeNode<T> *root)
{
if (root != nullptr)
{
_PostOrder(root->_left);
_PostOrder(root->_right);
cout << root->_data << ' ';
}
}
// 左单旋
void RotateL(Node *parent)
{
Node* sub = parent->_right;
Node* subL = sub->_left;
parent->_right = subL;
if (subL != nullptr)
subL->_parent = parent;
sub->_left = parent;
if (parent == _root)
{
parent->_parent = sub;
_root = sub;
sub->_parent = nullptr;
}
else
{
Node *parentparent = parent->_parent;
parent->_parent = sub;
if (parent == parentparent->_left)
parentparent->_left = sub;
else
parentparent->_right = sub;
sub->_parent = parentparent;
}
}
// 右单旋
void RotateR(Node *parent)
{
Node *sub = parent->_left;
Node *subR = sub->_right;
parent->_left = subR;
if (subR != nullptr)
subR->_parent = parent;
sub->_right = parent;
if (parent == _root)
{
_root = sub;
sub->_parent = nullptr;
parent->_parent = sub;
}
else
{
Node *parentparent = parent->_parent;
parent->_parent = sub;
if (parent == parentparent->_left)
parentparent->_left = sub;
else
parentparent->_right = sub;
sub->_parent = parentparent;
}
}
bool _IsRBTree(RBTreeNode<T> *root, size_t k, const size_t blackcount)
{
if (root == nullptr)
{
if (k != blackcount)
{
cout << "不满足每条路径中的黑色结点数目相同" << endl;
return false;
}
return true;
}
// 统计当前路径黑色结点数量
if (root->_col == black)
k++;
// 检测是否有连续的红色结点出现
Node *parent = root->_parent;
if (parent != nullptr && parent->_col == red && root->_col == red)
{
cout << "出现连续的红色结点" << endl;
return false;
}
return _IsRBTree(root->_left, k, blackcount) && _IsRBTree(root->_right, k, blackcount);
}
};