红黑树
一. 基于模拟2-3树而构造的红黑树的基本性质,重要!
-
红黑树是一种平衡二叉树
-
红黑树有两种节点,红节点red,黑节点black
-
连接到红节点的,都是左连接
-
没有任何一个节点连接两个红节点
-
黑节点 “ 完美平衡 ”,任意空节点到根节点之间,黑节点数量相同。
二. 红黑树的逻辑基础:2-3树
什么是2-3树?
一个用二节点和三节点组成的平衡树。
也就是可以二叉,可以三叉。
我们来做个示例,我料想此文读者都了解二叉树,如不了解,请先看二叉搜索树的相关文献。
我们从a插入到g看看
当出现4 节点,就需要升级,将中央节点 b 向上升位:
我们通过这种变换,使得树保持完美的平衡
下面是重点,看看插入e的时候发生了什么
(c d e) 又形成了四节点,如何变换?
d 进行了升位,加入到前序节点之中。使得树保持了完美的平衡。
又出现了四节点,需要升位
再升位,依旧完美平衡。
我们看到,虽然是按照顺序进行输入,但是并没有产生像二叉树的退化,无论如何变换,依旧是一个完美平衡的树。
三. 红黑树模拟2-3树
构建一颗2-3树不太容易,但用修改的二叉树实现比较容易,这就是红黑树。
我们通过向左连接红色节点来模拟3节点:
我们通过左右连接红节点模拟4节点
我们再通过从a插入到g看看
为了保持平衡,我们每次插入的都是红节点。
根节点和空节点都是黑节点。
插入红节点 b
出现右支红节点,需要变换。
从 a 指向 b 变为由 b 指向 a,
同时 b 和 a 的颜色调换
插入红节点 c
当同时出现左右向的红色节点,就需要变色
将左右节点 a,c 变为黑节点,中央 b 节点变为红节点,但由于 b 是根节点,必须为黑节点。
我们通过这种变换,使得树保持完美的平衡
出现了右支红节点,需要进行变换
下面是重点,看看插入e的时候发生了什么
c = d = e 又形成了左右红节点,如何变换?
d 进行了变色,使得树保持了完美的平衡,但是出现了右支的红节点,需要进行转换。
继续插入 f
继续变
最后插入g
消除 e = f = g 的左右红节点
消除 b = d = f 的左右红节点
完美平衡。
总结一下
我们只用了3个条件,就能构造出黑节点完美平衡的红黑树:
-
每次插入红节点
-
插入右支红节点后,需变换为左支红节点
-
当一个节点连接两个红节点时,进行变色
我们漏了一点,就是红节点不能连接红节点,但这种情况很多,如逆序插入,需要进行简单变换:
红节点 b 连接红节点 a,需要转换为
再变,当b不是根节点,需要变红。
所以再加一条
- 当红节点连接红节点,需要转换为中黑左右红的形式,并进一步变色
代码解释:插入
//左旋节点
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::rotateLeft(RBNode<K, V> *h)
{
RBNode<K, V> *x = h->right;
h->right = x->left;
x->left = h;
x->color = h->color;
h->color = RED;
x->N = h->N;
h->N = 1 + size(h->left) + size(h->right);
return x;
}
//右旋节点
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::rotateRight(RBNode<K, V> *h)
{
RBNode<K, V> *x = h->left;
h->left = x->right;
x->right = h;
x->color = h->color;
h->color = RED;
x->N = h->N;
h->N = 1 + size(h->left) + size(h->right);
return x;
}
//节点变色
template <typename K, typename V>
void ST::RBTree<K, V>::flipColors(RBNode<K, V> *h)
{
h->color = RED;
h->left->color = BLACK;
h->right->color = BLACK;
}
//put代码,插入节点
template <typename K, typename V>
void ST::RBTree<K, V>::put(const K &keys, const V &vals)
{
root = put(root, keys, vals); //调用递归put
root->color = BLACK; //重设根节点颜色
}
//递归put
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::put(RBNode<K, V> *h, const K &keys, const V &vals)
{
if (h == nullptr) //未匹配键key时初始化节点,返回节点内存地址
{
return new RBNode<K, V>(keys, vals, 1, RED);
}
if (keys < h->key) //插入的key小于节点key时,向左传递
{
h->left = put(h->left, keys, vals);
}
else if (keys > h->key) //插入的key大于节点key时,向右传递
{
h->right = put(h->right, keys, vals);
}
else //插入的key等于节点key,替换节点val
{
h->val = vals;
}
if (isRed(h->right) && !isRed(h->left)) //出现节点右支为红节点,左旋
{
h = rotateLeft(h);
}
if (isRed(h->left) && isRed(h->left->left)) //出现节点的左支及左支的左支为红节点,右旋
{
h = rotateRight(h);
}
if (isRed(h->left) && isRed(h->right)) //出现节点的左支和右支皆为红节点,改色
{
flipColors(h);
}
h->N = size(h->left) + size(h->right) + 1; //调整节点的size数值
return h;
}
四. 红黑树的节点删除
删除节点比插入节点要复杂一些,但仍然是可以理解的。
咱们先从最简单的开始,删除最小节点。
为了不影响平衡,删除的节点除根节点外,必须为红节点。
没有左键,直接删除。
原理很简单,没有左子节点的节点不能有右子节点,
因为右子节点的构造要求是黑节点,多出的黑节点导致红黑树黑节点不平衡,不能存在。
当左节点为红节点,直接向左传递
当左节点不为空,且不为红节点,变色,向左传递
以左连接红节点构造的二叉树,只有这三种形式
扩大到更多节点,也是如此,只需继续向左传递。
提高一点难度,删除最大节点。
没有左节点时候,直接删除。
左节点为红节点,要右旋,将右节点变红。
这也是插入右子节点的相反操作。
向右传递
当左节点不为空,且左节点为黑节点,需要还原为左右红节点,继续向右传递。
所以删除最大键也是三种形式:
和插入右节点是相反的操作。
接下来是最难的,但如果理解了删除最小键和最大键,就容易的多了。
删除一般键
删除普通键的策略是:
-
自根节点进行比较,如果小于本节点的key,按照删除最小键的方式向左传递。
-
如果大于本节点的key,按照删除最大键的方式向右传递。
-
如果等于本节点的key,如本节点的左节点为空,直接删除。
key == a;
-
如果左节点不为空,且为红节点,则本节点的key等于左子树的最大key,本节点的val等于左子树的最大key的val。删除左子树的最大节点。
key == b;
若左子树最大节点key为a:
-
如果左节点不为空,且为黑节点,则变色,本节点的key等于左子树的最大key,本节点的val等于左子树的最大key的val。删除左子树的最大节点。
key == b;
若左子树最大节点key为a:
再平衡
我们虽然是按照插入的逆方法进行删除,但有一个问题,就是如何再平衡。
按照插入的配平方法,我们可以依次有序平衡红黑树。
但是删除的时候,路径中可能出现以前未能涉及到的情况,所以,配平的代码较插入时的配平更为复杂。
- 左右红节点,直接变色。
- 右节点为红及右节点的左节点红,右旋转右节点
- 右节点为红,左旋右节点
- 同1:左右红节点,直接变色。
**注意:**第4条不是废话,因为经过2,3的变换可能会变成4。如果去除1,则当左右节点为红,右节点的左节点为红时,会不停的旋转为一条单链,无法平衡。
代码解释:删除:
//再平衡
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::balance(RBNode<K, V> *h)
{
if (isRed(h->left) && isRed(h->right))
{
flipColors(h);
h->N = size(h->left) + size(h->right) + 1;
return h;
}
if (isRed(h->right) && isRed(h->right->left))
{
h->right = rotateRight(h->right);
}
if (isRed(h->right))
{
h = rotateLeft(h);
}
if (isRed(h->left) && isRed(h->right))
{
flipColors(h);
}
h->N = size(h->left) + size(h->right) + 1;
return h;
}
//向左传递
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::toLeftRed(RBNode<K, V> *h)
{
if (isRed(h->left))
{
return h;
}
else
{
reflipColors(h);
return h;
}
}
//向右传递
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::toRightRed(RBNode<K, V> *h)
{
if (isRed(h->left))
{
h = rotateRight(h);
}
else
{
reflipColors(h);
}
return h;
}
//删除最小key的节点
template <typename K, typename V>
void ST::RBTree<K, V>::delMin()
{
if (root)
{
root = delMin(root);
if (root)
{
root->color = BLACK;
}
}
else
{
return;
}
}
//删除最大key的节点
template <typename K, typename V>
void ST::RBTree<K, V>::delMax()
{
if (root)
{
root = delMax(root);
if (root)
{
root->color = BLACK;
}
}
else
{
return;
}
}
//递归删除最小节点,返回删除后的指针
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::delMin(RBNode<K, V> *h)
{
if (!h->left)
{
delete h;
return nullptr;
}
h = toLeftRed(h);
h->left = delMin(h->left);
h = balance(h);
return h;
}
//递归删除最大节点,返回删除后的指针
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::delMax(RBNode<K, V> *h)
{
if (!h->left)
{
delete h;
return nullptr;
}
h = toRightRed(h);
h->right = delMax(h->right);
h = balance(h);
return h;
}
//删除一般节点
template <typename K, typename V>
void ST::RBTree<K, V>::del(const K &keys)
{
if (root)
{
root = del(root, keys);
}
if (root)
{
root->color = BLACK;
}
}
//取得节点的value,返回pair,first表示是否含有key,second是返回的value
template <typename K, typename V>
std::pair<bool, V> ST::RBTree<K, V>::get(const K &keys)
{
RBNode<K, V> *x = root;
while (x)
{
if (keys > x->key)
{
x = x->right;
}
else if (keys < x->key)
{
x = x->left;
}
else
{
return std::pair<bool, V>(true, x->val);
}
}
return std::pair<bool, V>(false, V());
}
//递归返回最大节点指针
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::max(RBNode<K, V> *x)
{
if (x->right == nullptr)
{
return x;
}
return max(x->right);
}
//递归删除一般节点
template <typename K, typename V>
RBNode<K, V> *ST::RBTree<K, V>::del(RBNode<K, V> *h, const K &keys)
{
if (keys < h->key)
{
if (h->left)
{
h = toLeftRed(h);
h->left = del(h->left, keys);
}
else
{
return h;
}
}
else if (keys > h->key)
{
if (h->right)
{
h = toRightRed(h);
h->right = del(h->right, keys);
}
else
{
return h;
}
}
else
{
if (!h->left)
{
delete h;
return nullptr;
}
else
{
h->key = max(h->left)->key;
h->val = get(h->left, h->key).second;
h = toLeftRed(h);
h->left = delMax(h->left);
}
}
return balance(h);
}