红黑树
1. 概念
红黑树是一种自平衡的二叉搜索树是一种高效的查找树。它是由 Rudolf Bayer 于1978年发明,在当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树,它在每个节点上增加了一个存储位来表示节点的颜色,可以是红色或黑色。它的平衡就是通过控制颜色限制,确保没有一条路径长度比另一条路径长出两倍达到的。
红黑树的出现是为了优化二叉搜索树查找复杂度为O(N)的情况,可是我们之前学的AVL树已经解决了这个问题,那红黑树出现的意义是什么?我们带着这个问题,去看下面的文章。
2. 性质
- 根节点是黑色的。
- 每个节点有且仅是红色或黑色。
- 如果一个节点是红色,那么它的左右孩子不能是红色(不能连续出现红色)。
- 每条路径的黑色节点个数必须一样。
满足上面性质就一定能保证没有一条路径长度比另一条路径长出两倍吗?
答案是对的,最长的路径一定红黑交替的,最短的路径一定是全黑的,所以这样就可以保证了。
3. 红黑树节点的定义
// 节点的颜色
enum Color{RED, BLACK};
// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{
RBTreeNode(const ValueType& data = ValueType(),Color color = RED)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _color(color)
{}
RBTreeNode<ValueType>* _pLeft; // 节点的左孩子
RBTreeNode<ValueType>* _pRight; // 节点的右孩子
RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给
出该字段)
ValueType _data; // 节点的值域
Color _color; // 节点的颜色
};
注:这里节点颜色默认是红色,记住这个特点,后面会有用。
4. 红黑树的结构
这里我们给红黑树增加一个头结点,这样可以使一些操作简化,该头结点的_pParent指向红黑树的根,_pLeft指向红黑树的最左节点,p_Right指向红黑树的最右节点。
5. 红黑树的操作
5.1 插入
在讲解插入之前之前,先简单了解一下几个概念:
- parent:父节点
- uncle:叔父节点( parent的兄弟节点)
- grand:祖父节点( parent 的父节点)
先是二叉搜索树,然后才是红黑树,所以我们先按照二叉搜索树规则去插入。插入之后我们需要维护红黑树的上述的性质,如果插入之后破坏了上述性质的任意一条,我们都需要调整。
我们上面知道默认插入节点的颜色是红色的,因为如果是黑色的会导致该路径上的黑色节点数比其他路径上的多,这种情况是无法调节的,所以默认为红色。那么好了,插入一个节点之后会出现哪几种情况呢?
-
如果插入节点的父节点是黑色,那么没有违反性质,不需要调整。
-
如果插入节点的父节点是黑色,那么违反了性质,需要调整。这种情况下也分两种情况。
(1)uncle节点存在并且为红色
这种情况的处理比较简单,我们只需要把p、u节点的颜色变成黑色,g的颜色变成红色,然后继续向上面调整,因为g变成了红色,直到遇到第一种情况,或者g为根结点,就调整完毕了。(2)uncle节点不存在或者存在为黑色
这种情况比较复杂,需要通过加上旋转才能解决。
如果需要旋转,就必须关注cur是p的左子树还是右子树,p是g的左子树还是右子树。
(2.1)p是g的左子树,cur也是p的左子树,这种情况我们只需要右单旋,然后是变色,只需要将p、g变色,p变黑,g变红。
(2.2)p是g的右子树,cur也是p的右子树,这种情况我们只需要左单旋,变色规则跟右单旋一样。
(2.3)p是g的左子树,cur是p的右子树,这种情况我们需要进行左右双旋,然后是变色,只需要将cur、g变色,cur变黑,g变红。
(2.4)p是g的右子树,cur是p的左子树,这种情况我们需要进行右左双旋,然后是变色,和左右双旋一样。
5.2 插入代码
pair<iterator,bool> insert(const T& data)
{
//插入头结点
if (_pHead->_parent == nullptr)
{
Node* node = new Node(data);
node->color = BLACK;
_pHead->_parent = node;
_pHead->_left = node;
_pHead->_right = node;
node->_parent = _pHead;
return make_pair(iterator(node),true);
}
Node* parent = nullptr;
Node* cur = _pHead->_parent;
KeyOfT kt;
while (cur)
{
if (kt(data) > kt(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else if (kt(data) < kt(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else
return make_pair(iterator(cur),false);
}
//默认插入红色的节点,黑色的难以控制
cur = new Node(data);
//因为cur位置会变
Node* newnode = cur;
if (kt(parent->_data) > kt(cur->_data))
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
//维护颜色,根据红黑树规则,不能出现连续的红色节点
while (parent != _pHead && parent->color == RED)
{
Node* grandFather = parent->_parent;
Node* uncle;
if (grandFather->_left == parent)
{
uncle = grandFather->_right;
//1. 情况一:叔叔存在并且颜色为红色
if (uncle && uncle->color == RED)
{
//只需要将叔叔、父亲变成黑色,爷爷变成红色,继续向上调整
uncle->color = BLACK;
parent->color = BLACK;
cur = grandFather;
parent = grandFather->_parent;
}
//2. 情况二:叔叔不存在或为黑色,无需向上调整
else if (!uncle || uncle->color == BLACK)
{
//需要旋转加变色
if (parent->_left == cur)
{
RotateR(parent);
parent->color = BLACK;
grandFather->color = RED;
}
else
{
RotateL(parent);
RotateR(grandFather);
cur->color = BLACK;
grandFather->color = RED;
}
break;
}
}
else
{
uncle = grandFather->_left;
//1. 情况一:叔叔存在并且颜色为红色
if (uncle && uncle->color == RED)
{
//只需要将叔叔、父亲变成黑色,爷爷变成红色,继续向上调整
uncle->color = BLACK;
parent->color = BLACK;
cur = grandFather;
parent = grandFather->_parent;
}
//2. 情况二:叔叔不存在或为黑色,无需向上调整
else if (!uncle || uncle->color == BLACK)
{
//需要旋转加变色
if (parent->_right == cur)
{
RotateL(parent);
parent->color = BLACK;
grandFather->color = RED;
}
else
{
RotateR(parent);
RotateL(grandFather);
cur->color = BLACK;
grandFather->color = RED;
}
break;
}
}
}
_pHead->_left = LeftMost();
_pHead->_right = RightMost();
_pHead->_parent->color = BLACK;
return make_pair(iterator(newnode), true);
}
6. 红黑树的验证
- 通过中序遍历,验证是不是二叉搜索树。
- 验证红黑树的性质。
// 检测红黑树是否为有效的红黑树,
bool _IsValidRBTRee(Node* pRoot, size_t blackCount, size_t pathBlack)
{
if (!pRoot)
{
return blackCount == pathBlack;
}
if (pRoot->color == BLACK)
blackCount++;
else
{
if (pRoot->_parent->color == RED)
return false;
}
return _IsValidRBTRee(pRoot->_left, blackCount, pathBlack) && _IsValidRBTRee(pRoot->_right, blackCount, pathBlack);
}
bool IsValidRBTRee()
{
Node* cur = _pHead->_parent;
if (!cur)
return true;
if (cur->color == RED)
return false;
int pathBlack = 0;
//计算一条路径上黑色节点个数
while (cur)
{
if (cur->color == BLACK)
pathBlack++;
cur = cur->_left;
}
_IsValidRBTRee(cur, 0, pathBlack);
}
7. 红黑树效率
7.1 效率
红黑树的效率主要体现在插入、删除和查找操作的时间复杂度上。红黑树是一种自平衡的二叉搜索树,其插入、删除和查找操作的时间复杂度都是O(logn),其中n是红黑树中节点的数量。
7.2 和AVL树比较
- AVL树的时间复杂度虽然优于红黑树,但是对于现在的计算机,cpu太快,可以忽略性能差异
- 红黑树的插入删除比AVL树更便于控制操作
- 红黑树整体性能略优于AVL树(红黑树旋转情况少于AVL树)
8. 红黑树应用
- C++ STL库 – map/set、mutil_map/mutil_set
- Java 库
- linux内核
- 其他一些库