红-黑树基本概念
红-黑树(Red-Black tree)是一种特殊的二叉查找树,因此具有二叉查找树的特征:任意一个节点所包含的关键字,大于左孩子的关键字,小于右孩子的关键字。树中每一个节点的颜色或者是黑色或者是红色,和AVL树一样具有平衡性。每一个空指针用一个外部结点来替代,红-黑树的性质可以描述为:
RB1:根节点和外部节点都是黑色。
RB2:在根至外部节点路径上,没有连续两个节点是红色。
RB3:在所有根至外部节点的路径上,黑色节点的数目都相同。
还有一种描述,其取决于父子指针的颜色,由父节点指向红色孩子的指针颜色为红,由父节点指向黑色儿子的指针颜色为黑。所以上述性质可以等价描述为:
RB1’:从内部节点指向外部节点的指针是黑色的。
RB2’:在跟至外部节点路径上,没有连续两个指针式红色的。
RB3’:在所有根至外部节点的路径上,黑色指针的数目都相同。
红-黑树示意图如下:
红-黑树的C++实现
对于节点颜色和指针颜色的性质描述,我们以下的讨论只针对节点颜色,相应的指针的颜色变化很容易推断出来。
1、红-黑树的节点
#pragma once
enum RBTreeColor{RED,BLACK};
template <typename T>
struct RBTreeNode
{
RBTreeColor color;
T key;
RBTreeNode<T>* leftChild;
RBTreeNode<T>* rightChild;
RBTreeNode<T>* parent;
RBTreeNode(T key, RBTreeColor c, RBTreeNode<T>* l,
RBTreeNode<T>* r, RBTreeNode<T>* p) :
key(value), color(c), leftChild(l), rightChild(r), parent(p) { }
RBTreeNode(T key, RBTreeColor c) : key(key), color(c),
leftChild(nullptr), rightChild(nullptr), parent(nullptr) { }
};
数据成员说明:
color:节点的颜色
key:关键字
leftChild:节点的左孩子
rightChild:节点的右孩子
parent:节点的父亲
使用enum定义了一个枚举类型对象,节点要么是黑色,要么是红色。
2、红-黑树类的接口
//***RBTree.h
#pragma once
#include<iostream>
#include"RBTreeNode.h"
using namespace std;
template <typename T>
class RBTree
{
public:
RBTree() : root(nullptr) { }
~RBTree() { destroy(root); }
// 前序、中序、后序遍历红-黑树
void preOrder() { preOrder(root); }
void inOrder() { inOrder(root); }
void postOrder() { postOrder(root); }
RBTreeNode<T>* find(const T& theKey) const;
// 查找最小和最大结点:返回结点的关键字指针。
T* minimum();
T* maximum();
// 将结点(theKey为节点关键字)插入到红黑树中
void insert(const T& theKey);
// 删除结点(theKey为节点关键字)
void erase(const T& theKey);
// 销毁红黑树
void destroy();
void output() { inOrder(root); cout << endl; }
private:
RBTreeNode<T>* root;
// 前序、中序、后序遍历红-黑树
void preOrder(RBTreeNode<T>* theRoot) const;
void inOrder(RBTreeNode<T>* theRoot) const;
void postOrder(RBTreeNode<T>* theRoot) const;
// 查找最小结点:返回以theRoot为根结点的红-黑树的最小结点
RBTreeNode<T>* minimum(RBTreeNode<T>* theRoot);
// 查找最大结点:返回以theRoot为根结点的红-黑树的最大结点
RBTreeNode<T>* maximum(RBTreeNode<T>* theRoot);
// 左旋
void rightRotate(RBTreeNode<T>* gu);
// 右旋
void leftRotate(RBTreeNode<T>* gu);
// 插入修正函数
void insertFixUp(RBTreeNode<T>* u);
// 删除修正函数
void eraseFixUp(RBTreeNode<T> *y, RBTreeNode<T> *py);
// 销毁红黑树
void destroy(RBTreeNode<T>* &theRoot);
};
对于插入和删除操作,首先和二叉查找树一样进行插入和删除操作,若造成红-黑树不平衡,再用insertFixUp()和eraseFixUp()进行修正。
3、旋转操作
当红-黑树失去平衡时,需要进行颜色变化和选择操作,类似于AVL的旋转,分为左旋和右旋两种:
leftRotate:
/*********************************
***leftRotate:
* gu pu
* / \ / \
* guL pu ----> gu puR
* / \ / \
* puL puR guL puL
**********************************/
template <typename T>
void RBTree<T>::leftRotate(RBTreeNode<T>* gu)
{
RBTreeNode<T>* pu = gu->rightChild;
gu->rightChild = pu->leftChild;
if (pu->leftChild != nullptr)
pu->leftChild->parent = gu;
pu->parent = gu->parent;
if (gu == root)
root = gu;
else
{
if (gu == gu->parent->leftChild)
gu->parent->leftChild = pu;
else
gu->parent->rightChild = pu;
}
pu->leftChild = gu;
gu->parent = pu;
}
rightRotate:
/*********************************
***rightRotate:
* gu pu
* / \ / \
* pu guR ----> puL gu
* / \ / \
* puL puR puR guR
**********************************/
template <typename T>
void RBTree<T>::rightRotate(RBTreeNode<T>* gu)
{
RBTreeNode<T>* pu = gu->leftChild;
gu->leftChild = pu->rightChild;
if (pu->rightChild != nullptr)
pu->rightChild->parent = gu;
pu->parent = gu->parent;
if (gu == root)
root = pu;
else
{
if (gu == gu->parent->leftChild)
gu->parent->leftChild = pu;
else
gu->parent->rightChild = pu;
}
pu->rightChild = gu;
gu->parent = pu;
}
4、插入
将一个节点插入到红-黑树中,和二叉查找树一样,但是因为节点是有颜色的,所以可能导致树失去平衡。如果插入的是“黑色”节点,将一定违反RB3(根到外部节点的黑色节点数目不等),而如果插入的是“红色”节点,有可能违反RB2(连续两个红色节点),也有可能不违反。所以,我们将新插入的节点染成“红色”,按照二叉查找树的方法插入到红-黑树中,若失去平衡,再进行修正。
插入:
template <typename T>
void RBTree<T>::insert(const T& theKey)
{
//创建新节点,并着色为“红色”
RBTreeNode<T>* newNode =
new RBTreeNode<T>(theKey, RED);
if (newNode == nullptr)
return;
RBTreeNode<T>* p = root,
*pp = nullptr;
while (p != nullptr)
{
pp = p;
if (theKey < p->key)
p = p->leftChild;
else if (theKey > p->key)
p = p->rightChild;
else
{
cerr << "不允许添加相同的节点!" << endl;
return;
}
}
newNode->parent = pp;
if (pp != nullptr)
{
if (theKey < pp->key)
pp->leftChild = newNode;
else
pp->rightChild = newNode;
}
else
root = newNode;
//修正添加newNode造成的红-黑树失去平衡
insertFixUp(newNode);
}
不平衡的类型可以通过检查新节点u、其父节点pu及其祖父节点gu来确定。RB2性质被破坏时,即连续两个红色节点:一个是u,另一个必定是它的父节点pu,因此pu存在。根据u的叔叔节点的颜色和父节点的位置,可以分为六种情况:
a、父节点是左孩子,叔叔节点是红色, 对应LLr或LRr,只需要进行颜色变化。将pu和guR变成“黑色”,gu变成“红色”。若gu的父节点存在且为“红色”,将转变成a、b、c、d、e或f情况,继续迭代。
代码:
if (guR && guR->color == RED)
{
pu->color = BLACK;
guR->color = BLACK;
gu->color<