红黑树及相关操作

红黑树

红黑树是一种常用的平衡二叉树,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: 数据结构与算法分析

    原文作者:算法小白
    原文地址: https://blog.csdn.net/weixin_40170902/article/details/80877147
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞