对红黑树的一些理解及实现
红黑树(英语:Red–black tree)是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
以下是我对红黑树的一些理解,可供参考,不保证准确性。
参考资料:
https://www.cnblogs.com/alantu2018/p/8462017.html
https://zhuanlan.zhihu.com/p/22800206
最后实现的代码放在Github上:
https://github.com/Radium1209/Red-Black-Tree
特性
- 每个节点是黑色或红色(所以叫红黑树)
- 根节点是黑色的
- 叶节点是黑色的(空的叶节点)
- 如果一个节点为红,子节点必须为黑
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点
- 为什么根节点是黑色的?
我的理解是使前两层一定满足红黑树条件,不然在前两层就需要进行旋转操作(见后文),这样会使旋转操作更加复杂(因为要考虑祖父节点不存在),且旋转后的根节点也会变黑,因此不如一开始就令根节点为黑。 - 为什么叶子节点是黑色的?
这个和后面旋转时操作的叔叔节点有关,因为叔叔节点为空的话也为黑色,这样满足其中一个旋转条件。 - 特性4和特性5
这两个特性是用来保持平衡的,红黑树是一种近似平衡的树,特性5保证了没有一条路径会比其他路径长出2倍,所以可以保持红黑树的近似平衡。红黑树的近似平衡是因为它的旋转操作会比较少(与AVL树相比),所以红黑树在插入删除的性能要好于AVL树,在查询上可能会略逊于AVL树。
主要操作
- 变颜色
- 左旋
- 右旋
变颜色
就是改变一下颜色,总共就两种。
左旋
关于旋转的部分网上的资料很多,可以自行搜索,这边主要讲一下关于代码的部分。
左旋示意图(对节点x进行左旋):
px px
/ /
x y
/ \ --(左旋)--> / \
lx y x ry
/ \ / \
ly ry lx ly
以上图为例,左旋所改变的部分是:
px->Child = y // 可能是左孩子也有可能是右孩子
x->father = y // x的父亲和右儿子都变了
x->rightChild = ly
y->father = px // y的父亲和左儿子都变了
y->leftChild = x
ly->father = x // ly的父亲变了
所以我们的代码就直接改变这些变化的值就可以了,不过需要注意一些值可能为空:
template<class T>
void RBTree<T>::leftRotate( RBTNode<T> *node )
{
RBTNode<T> *x = node;
RBTNode<T> *y = x->rightChild;
// 修改x->rightChild = ly
x->rightChild = y->leftChild;
// 修改ly->father = x, 注意ly可能为空
if ( y->leftChild != NULL)
{
y->leftChild->father = x;
}
// 修改y->father = px
y->father = x->father;
// 修改px-Child = y,此时需要考虑px为NULL以及左右孩子的区别
if ( x->father == NULL )
{
root = y; // 此时要更新root
}
else
{
if ( x->father->leftChild == x )
{
x->father->leftChild = y;
}
else
{
x->father->rightChild = y;
}
}
// 修改y->leftChild = x
y->leftChild = x;
// 修改x->father = y
x->father = y;
}
右旋
和左旋基本上一样:
template<class T>
void RBTree<T>::rightRotate( RBTNode<T>* node )
{
RBTNode<T> *y = node;
RBTNode<T> *x = y->leftChild;
y->leftChild = x->rightChild;
if ( x->rightChild != NULL )
{
x->rightChild->father = y;
}
x->father = y->father;
if ( y->father == NULL )
{
root = x;
}
else
{
if ( y->father->leftChild == y )
{
y->father->leftChild = x;
}
else
{
y->father->rightChild = x;
}
}
x->rightChild = y;
y->father = x;
}
插入
正常插入
以上操作都是为了插入和删除做准备的,因为在插入和删除的时候会使红黑树不满足红黑树的特性,所以需要用以上操作对红黑树进行修正。下面讲一下在插入时如何对红黑树进行修正。
首先按照正常的二叉搜索树进行插入,且插入的节点为红色。
- 插入的节点为什么是红色?
如果插入的是黑色永远不会破坏特性4,而且所有节点都变成黑色了,根本没有意义。
/* 插入 */
template<class T>
void RBTree<T>::insert( RBTNode<T>* node )
{
RBTNode<T> *y = NULL;
RBTNode<T> *x = root;
// 当做二叉排序树正常插入
while ( x != NULL )
{
y = x;
if ( node->value < x->value )
{
x = x->leftChild;
}
else
{
x = x->rightChild;
}
}
node->father = y;
if ( y != NULL )
{
if ( node->value < y->value)
{
y->leftChild = node;
}
else
{
y->rightChild = node;
}
}
else