红黑树
红黑树是一种常用的平衡二叉树,C++标准模板库中的set正是基于红黑树构造。红黑树具有以下几个性质:
- (1) 每个节点被着色成红色或黑色。
- (2) 树的根节点是黑色的。
- (3) 如果一个节点是红色的,则它的子节点必须是黑色的(即两个红色的节点不能连续出现)。
- (4) 从一个节点到一个NULL指针的没一条路径必须包含相同数目的黑色节点。
一个红黑树的例子
自底向上红黑树
由于条件(4)的存在,使得如果新插入的项为叶节点,则该节点的颜色必须为红色。如果插入之后其父节点为黑色,则插入完成。比如上图红黑树中插入7。
而如果新节点的父节点为红色(比如上图插入36),则出现了连续的两个红色节点,不满足条件(3)。则此时需要对树进行调整(颜色改变和旋转)。
- 一字形旋转(单旋转)
如下图假如要插入的新的叶节点为X,P为其父节点,S为P的兄弟节点,G为X的祖父节点。X和P都是红色,则需要对树结构进行调整。先基于P和G做一次单左旋,然后对新根着黑色,新根的两个孩子都着红色。
- 之字形旋转(双旋转)
下图对应需要进行双(左)旋转的情况,而双旋转可以分成两次单旋转,即现在P和X进行一次单右旋然后再G和X之间进行一次单左旋。
然而,当将79插入至本例的二叉树中时,由于其父节点80及其兄弟节点85都是红色的,以至于旋转过后依然会出现两个红色连续的情况。即:
这种情况出现时,需要从底部向上进行“上滤”的不断调整树使得树满足红黑树的条件。
自顶向下红黑树
自底向上时,上滤的操作需要用到栈或父级指针保存路径。自顶向下类似于伸展树的过程保证不出现连续两个红节点。
在向下的过程中,如果一个节点X有两个红孩子,则可以让X变成红的,两个孩子变成黑的。这种简单颜色反转也可能会造成连续的红节点出现(当X的父节点P也是红色的时候)这时需要对树进行旋转操作。而X父节点P的兄弟节点不可能为红色(自顶向下的保证)
例如,要将45插入到下图的红黑树中:
在自顶向下的过程中,发现50的两个孩子40和55都为红色,则反转颜色50为红色,45和55都为黑色。这时出现了50和60两个连续红色则需要在70和60之间进行一次单左旋。使得60变成30的右孩子着色为黑,50和70分别成为60的左右孩子都着色为红。最后在黑节点40直接插入新的红节点45,使得整个树满足红黑树的条件。
红黑树实现及相关操作C/C++
#include<iostream>
using namespace std;
#define ElementType int
enum ColorType {Red, Black}; // 定义颜色为枚举类型
typedef struct TreeNode {
ElementType value; // 节点关键字值
TreeNode* left; // 左孩子
TreeNode* right; // 右孩子
ColorType color; // 节点颜色
}RedBlackNode, *RedBlackTree;
RedBlackNode* NullNode = NULL; // 定义空节点
RedBlackTree Initialize() // 初始化树
{
if (NullNode == NULL) // 如果空节点为空指针
{
NullNode = new RedBlackNode; // 为空结点分配空间
NullNode->left = NullNode->right = NullNode; // 左右孩子赋值为空结点
NullNode->color = Black; // 空节点颜色初始化为黑色
NullNode->value = INFINITY; // 空节点的值初始化为无穷大
}
RedBlackTree RBTree = new RedBlackNode; // 创建一棵树,为根节点分配空间
RBTree->value = -INFINITY; // 初始化树根节点为无穷小,实际上真正的有关键字的根节点为RBTree->right
RBTree->left = RBTree->right = NullNode; // 左右孩子赋值为空节点
RBTree->color = Black;
return RBTree;
}
RedBlackTree SingleRotateWithLeft(RedBlackTree K2) // 单左旋函数
{
RedBlackNode* K1 = new RedBlackNode; // 创建临时结点K1指向K2的左孩子
K1 = K2->left;
K2->left = K1->right; // K1的右孩子变成K2的左孩子
K1->right = K2; // K1的右孩子变成K2,此时K1转变为子树的根节点
return K1;
}
RedBlackTree SingleRotateWithRight(RedBlackTree K2) // 单右旋函数,调整以K3为根结点的子树
{
RedBlackNode* K1 = new RedBlackNode;
K1 = K2->right;
K2->right = K1->left;
K1->left = K2;
return K1;
}
static RedBlackTree Rotate(RedBlackNode* Parent, ElementType key) // 定义旋转函数(整合左右旋转),表示插入关键字key旋转后连接到父节点Parent
{
if (key < Parent->value) // 当key小于父节点的关键字时,考虑父节点的左孩子
{
if (key < Parent->left->value) // 如果key小于左孩子关键字时,则以其左孩子为参考点进行单左旋
{
return Parent->left = SingleRotateWithLeft(Parent->left);
}
else // // 如果key大于左孩子关键字时,则以其左孩子为参考点进行单右旋
return Parent->left = SingleRotateWithRight(Parent->left);
}
else
return Parent->right = key > Parent->right->value ?
SingleRotateWithRight(Parent->right) : SingleRotateWithLeft(Parent->right);
}
static RedBlackTree X, Pa, GPa, GGPa; // 定义当前节点X,X的父节点Pa,X的祖父节点GPa,X的曾祖父节点GGPa
static void HandleReorient(RedBlackTree RBTree, ElementType key)
{ // 处理两个孩子都为红节点的情况
X->color = Red; // 反转颜色
X->left->color = X->right->color = Black;
if (Pa->color == Red) // 如果X的父节点Pa也为红色,则必须进行旋转
{
GPa->color = Red; // X的祖父节点颜色设为红色
if ((key < GPa->value) != (key < Pa->value)) // 如果关键字key大小介于X父节点Pa和祖父节点GPa的关键字之间时
Pa = Rotate(GPa, key); // 以GPa为父节点执行一次单旋转
X = Rotate(GGPa, key); // 以GGpa为父节点执行一次单旋转。两次单旋转也就是一次双旋转
}
RBTree->right->color = Black; // 根节点着色为黑
}
RedBlackTree Insert(RedBlackTree RBTree, ElementType key)
{
X = Pa = GPa = GGPa = RBTree; // 初始化X,Pa,GPa,GGPa为树的根节点
NullNode->value = key; // 定义一个空节点的关键字值为key
while (X->value != key) // 自顶向下迭代进行
{
GGPa = GPa; // 曾祖父更新为祖父
GPa = Pa; // 祖父更新为父节点
Pa = X; // 父节点更新为当前节点
if (key < X->value)
X = X->left; // 递归向左
else
X = X->right; // 递归向右
if (X->left->color == Red && X->right->color == Red) // 当前节点的左右孩子都为红节点
HandleReorient(RBTree, key); // 调整树
}
if (X != NullNode)
return NullNode;
X = new RedBlackNode; // 为X申请空间
X->value = key;
X->left = X->right = NullNode;
if (key < Pa->value) // 连接到其父节点Pa
Pa->left = X;
else
Pa->right = X;
HandleReorient(RBTree, key); // 当插入X后,还可能会出现两个红色兄弟节点,重新调整树
return RBTree;
}
static void InOrderPrint(RedBlackTree RBTree) // 中序遍历树
{
if (RBTree != NullNode)
{
InOrderPrint(RBTree->left);
cout << RBTree->value << " ";
InOrderPrint(RBTree->right);
}
}
void PrintTree(RedBlackTree RBTree)
{
InOrderPrint(RBTree->right);
}
int main()
{
int A[] = { 10,85,15,70,20,60,30,50,65,80,90,40,5,55 }; RedBlackTree myRBTree = Initialize(); for (int i = 0;i < sizeof(A) / sizeof(int);i++) { myRBTree = Insert(myRBTree, A[i]); } PrintTree(myRBTree); cout << endl; system("pause"); return 0; }
输出结果
参考资料
Mark Allen Weiss: 数据结构与算法分析