数据结构——红黑树(附C++实现代码)

定义

红黑树是一种自平衡的二叉搜索树。每个节点额外存储了一个color字段(“RED” or “BLACK”),用于确保树在插入和删除时保持平衡

性质

一棵合法的红黑树必须遵循以下条性质:

  1. 是二叉搜索树,即中序遍历是顺序排列(左根右)
  2. 根节点和NIL节点(空叶子节点)为黑色(根叶黑)
  3. 红色节点的子节点为黑色(或者说没有两个连续的红色节点)(不红红)
  4. 从根节点到NIL节点的每条路径上的黑色节点数量相同(黑路同)

结构

红黑树类的定义
template> 
class RBTree{
private:
   enum Color{Black,Red};
   struct Node{
   	......
   };
   Compare compare;
   size_t size;
   Node *root;
   
   void rotateLeft(Node *node);
   void rotateRight(Node *node);
   Node*& findByKey(Key key,Node** parent=nullptr);
   void maintainAfterInsert(Node *node);
   void maintainAfterRemove(Node *node);

public:
   RBTree(){
       this->compare = Compare();
       this->size = 0;
       this->root = nullptr;
   }
   int insert(Key key, Value value);
   int remove(Key key);
   void traversal(int type);
};
节点维护的信息
节点名 类型 描述
left Node* 左子节点指针
right Node* 右子节点指针
parent Node* 父节点指针
color enum{BLACK,RED} 颜色枚举
key Key 节点键值,具有唯一性和可排序性
value Value 节点内存储的值
节点结构定义
struct Node{
    struct Node *left = nullptr;
    struct Node *right = nullptr;
    struct Node *parent = nullptr;
    Color color;
    Key key;
    Value value;
    Node(Key key, Value value,struct Node* parent)
        : left(nullptr),right(nullptr), parent(parent), 
        color(Red), key(key), value(value) {}

    // 获得兄弟节点
    inline struct Node* sibling() const noexcept{
        return this->parent->left == this ? this->parent->right : this->parent->left;
    }

    // 获得叔叔节点
    inline struct Node* uncle() const noexcept{
        return this->parent->sibling();
    }

    // 获得祖父节点
    inline struct Node* grandParent() const noexcept{
        return this->parent->parent;
    }

    // 判断当前不平衡类型(LL,LR,RL,RR)-> (0,1,2,3)
    inline size_t unbalancedType() const noexcept{
        size_t type = 0;
        type |= (this->parent->left == this ? 0 : 1);
        type |= (this->grandParent()->left == this->parent ? 0 : 2);
        return type;
    }
};

前置说明

旋转操作

旋转操作是用于保持树的平衡性的重要机制。旋转操作不会改变二叉查找树(BST)的性质,即左子节点的关键值小于父节点,右子节点的关键值大于父节点。旋转操作分为两种:左旋(rotateLeft)和右旋(rotateRight)。它们主要用于调整树结构以维持红黑树的性质。

左旋

左旋操作是以当前点为旋转点,以其右孩子作为旋转中心,逆时针旋转替代旋转中心的左孩子,此时旋转中心的左孩子被占,而旋转点右孩子空缺,因此将旋转中心的原左孩子变成旋转点的右孩子,最后将旋转中心提升到旋转点的位置

实现代码
/**
 * @brief  左旋操作
 * @param  node 旋转点     
 * @return void         
 */
template 
void RBTree::rotateLeft(Node *node){
    //     |                         |
    //     L       l-rotate(L)       R     
    //    / \      ==========>      / \
    //   T1  R                     L   T3
    //      / \                   / \
    //    T2   T3               T1   T2  
    Node *temp = node->right;
    // 需要修改三对关系
    // 第一对
    node->right = temp->left;
    if(temp->left != nullptr){
        temp->left->parent = node;
    }

    // 第二对
    temp->parent = node->parent;
    if(node->parent == nullptr){
        this->root = temp;
    }else if(node == node->parent->left){
        node->parent->left = temp;
    }else{
        node->parent->right = temp;
    }

    // 第三对
    temp->left = node;
    node->parent = temp;
}
右旋

右旋操作是以当前点为旋转点,以其左孩子作为旋转中心,顺时针旋转替代旋转中心的右孩子,此时旋转中心的右孩子被占,而旋转点左孩子空缺,因此将旋转中心的原右孩子变成旋转点的左孩子,最后将旋转中心提升到旋转点的位置

实现代码
/**
 * @brief  右旋操作
 * @param  node 旋转点
 * @return void
 */
template 
void RBTree::rotateRight(Node *node){
    //     |                         |
    //     R       r-rotate(R)       L     
    //    / \      ==========>      / \
    //   L  T3                     T1  R
    //  / \                           / \
    // T1  T2                        T2  T3  
    Node* temp = node->left;
    // 需要修改三对关系
    // 第一对
    node->left = temp->right;
    if(temp->right != nullptr){
        temp->right->parent = node;
    }

    // 第二对
    temp->parent = node->parent;
    if(node->parent == nullptr){
        this->root = temp;
    }else if(node->parent->left == node){
        node->parent->left = temp;
    }else{
        node->parent->right = temp;
    }

    // 第三对
    temp->right = node;
    node->parent = temp;
}

查找节点

红黑树本身也是二叉搜索树(BST),可以直接按照二叉搜索树的搜索流程进行

实现代码
/**
 * @brief  查找节点,返回结果的引用
 * @param  key 查询键值
 * @param  parent 不为空则保存查询节点的父亲作为返回值的一部分
 * @return Node*& 返回查询结果的引用
 */
template 
typename RBTree::Node*& RBTree::findByKey(Key key,Node** parent){
    Node **cur = &this->root;
    while (*cur){
        if (key == (*cur)->key) break;
        if(parent) *parent = *cur;
        cur = this->compare(key,(*cur)->key) ? &(*cur)->left : &(*cur)->right;
    }
    return *cur;
}

符号约定

视为红色节点

[N] 视为黑色节点

(N) 视为未知颜色节点

N 视为未知存在节点

红黑树的插入

插入节点

首先检查要插入的节点是否已存在,若存在,则直接更新键值并返回,否则作为一个新的红色节点直接插入,同时直接插入红色节点有可能会违反性质3(不红红),因此需要对新插入的节点进行维护

实现代码
/**
 * @brief  插入或更新节点
 * @param  key 新节点的key
 * @param  value 新节点的value
 * @return int  0表示插入成功,1表示节点已存在,更新value
 */
template 
int RBTree::insert(Key key, Value value){
    Node *parent = nullptr;
    Node *&node = this->findByKey(key,&parent);
    if(node){
        node->value = value;
        return 1;
    }
    node = new Node(key,value,parent);
    this->size++;
    this->maintainAfterInsert(node);
    return 0;
}

插入节点后的维护

插入新节点后可能出现多种情况,我们一一进行分析并给出解决操作

case 1: 插入的节点是根节点

分析:性质2要求根节点为黑

操作:直接将节点染黑,返回

代码:

// (N) ===> [N]
if(node->parent == nullptr){
    node->color = Black;
    return ;
}
case 2: 插入的节点的父亲是黑色

分析:不会违反任何一条性质

操作:直接返回

代码:

// [P]
//  |  
//  
if(node->parent->color == Black) return ;
case 3: 插入的节点是父亲是红色,且叔叔不存在或为黑色

分析:

  1. 由于父亲是红色,则其一定也有父亲,即祖父节点存在,否则父亲是根节点会违反性质2。

  2. 祖父节点存在且一定为黑色,否则违反性质3

    注意:理论上对新插入的节点而言,其叔叔要么不存在,要么为红,之所以还是要判断叔叔是否为黑色,是因为后续可能继续维护新插入节点的祖先节点,而祖先节点的叔叔可以为黑

操作:

  1. 旋转:根据节点、父亲、祖父三代构成的不平衡类型(LL LR RL RR)进行同AVL树维护平衡时一样的旋转操作
  2. 染色:此时父亲会取代祖父的位置,需要继承祖父的黑色,而祖父需要染红

代码:

// 此处举例LR型,本质上是先转换成LL型,再按照LL型进行旋转,RL型同理
//      |                         |                         |                    |
//     [G]                       [G]                                         [N]
//     / \       l-rotate(P)     / \       r-rotate(G)     / \      paint       / \
//   

U?|[U] ==========> U?|[U] ==========>

[G] ========>

// \ / \ \ //

U?|[U] U?|[U] if(!uncle || uncle->color == Black){ switch (node->unbalancedType()){ case 0: // LL this->rotateRight(grandParent); break; case 1: // LR this->rotateLeft(parent); this->rotateRight(grandParent); break; case 2: // RL this->rotateRight(parent); this->rotateLeft(grandParent); break; case 3: // RR this->rotateLeft(grandParent); break; } // 祖父染红 grandParent->color = Red; // 无论是什么类型,最后祖父的父亲肯定是顶替其原来位置的节点,染红 grandParent->parent->color = Black; return ; }

case 4: 插入的节点是父亲是红色,且叔叔也为红色

分析:

  1. 此时违反的是性质3,因此可以将祖父节点的黑色下放到两个红孩节点,并且祖父染红,这样以祖父为根的红黑树不会冲突
  2. 将祖父染红可能会违反性质2或性质3,和插入新节点可能会引起的不平衡的情况相同,因此继续维护祖父节点

操作:

1.红色下放:将父亲和叔叔染黑,祖父染红

2.维护祖父:继续维护祖父节点

代码:

//      |                      |
//     [G]                    
//     / \       paint        / \    maintain(G)
//   

==========> [P] [U] ==========> // \ \ // parent->color = Black; uncle->color =Black; grandParent->color = Red; this->maintainAfterInsert(grandParent);

红黑树的删除

删除节点

首先检查要删除的节点是否存在,若不存在,则删除失败,直接返回,若存在,则需要根据删除节点的孩子数量进行分析

case 1:被删除节点有2个孩子

操作:找到被删除节点的直接后继(或直接前驱),交换两个节点的键值对,并将要删除的节点变成直接后继(或直接前驱),由于直接后继(或直接前驱)至多有一个有一个孩子,因此将被删除节点有2个孩子的情形转换成了被删除节点有0个或1个孩子的情形

注:直接前驱指的是中序遍历中该节点的前一个元素,直接后继指的是中序遍历中该节点的后一个元素

case 2:被删除节点有1个孩子

分析:在红黑树中,若一个节点仅有一个孩子,则必然是父黑子红的结构,因此可以直接将孩子提升到父亲的位置,并染黑即可

操作:将唯一的孩子提升到父亲的位置,并染黑

case 3:被删除节点有0个孩子,且被删除节点为红色

操作:直接删除即可

case 4:被删除节点有0个孩子,且被删除节点为黑色

分析:这是最为复杂的情形,在正式删除该节点前,需要对该节点进行删除节点后的维护

操作:维护被删除的节点

实现代码
/**
 * @brief  删除节点(若存在)
 * @param  key 需要删除节点的key
 * @return int  0表示删除成功,1表示节点不存在,删除失败
 */
template 
int RBTree::remove(Key key){
    Node* node = this->findByKey(key,nullptr);
    if(!node) return 1;
    
    // case 1:被删除节点有2个孩子
    // 操作:找到被删除节点的直接后继(或直接前驱),交换两个节点的键值对,并将要删除的节点变成直接后继(或直接前驱),由于直接后继(或直接前驱)至多有一个有一个孩子,因此将被删除节点有2个孩子的情形转换成了被删除节点有0个或1个孩子的情形
    // 注:直接前驱指的是中序遍历中该节点的前一个元素,直接后继指的是中序遍历中该节点的后一个元素
    if(node->left && node->right){
        Node *temp = node;
        node = node->right;
        while(node->left) node = node->left;
        temp->key = node->key;
        temp->value = node->value;
    }
    
    Node *parent = node->parent;
    Node *child = node->left ? node->left : node->right;

    // case 2:被删除节点有1个孩子
    // 分析:在红黑树中,若一个节点仅有一个孩子,则必然是父黑子红的结构,因此可以直接将孩子提升到父亲的位置,并染黑即可
    // 操作:将唯一的孩子提升到父亲的位置,并染黑
    if(child){
        child->color = Black;
    }
    // case 3:被删除节点有0个孩子,且被删除节点为红色
    // 操作:直接删除即可

    // case 4:被删除节点有0个孩子,且被删除节点为黑色
    // 分析:这是最为复杂的情形,在正式删除该节点前,需要对该节点进行删除节点后的维护
    // 操作:维护被删除的节点
    else if(node->color == Black){
        this->maintainAfterRemove(node);
    }

     // 重建关系,正式删除
    if(parent) parent->left == node ? parent->left = child : parent->right = child;
    else this->root = child;
    if(child) child->parent = parent;
    
    delete node;
    this->size--;
    return 0;                                                         
}

删除节点后的维护

需要执行删除节点后的维护说明被删除的节点是一个没有孩子的黑色节点,这时候删除了该节点会导致性质4(黑路同)被破坏,为了解决这个问题,我们可以引入一个双黑节点的概念,其可以有两种理解方式:

  1. 经过双黑节点的路径的黑色节点数量额外加一
  2. 经过双黑节点的路径相较其他不经过双黑节点的路径,黑色节点数量少一

我们接下来一一分析双黑节点不同的情况并给出解决操作

case 1:双黑节点是红色节点

分析:可以将节点染黑来弥补经过双黑节点少掉的一个黑色节点,而由于此时的双黑节点一定有两个孩子(此时双黑节点是由黑色节点上移,其左右子树都必须含有黑色节点来保证性质4)

操作:将双黑节点染黑,维护结束

代码:

//  ===> [N]
if (node->color == Red){
    node->color = Black;
    return;
}
case 2:双黑节点为根节点

分析:此时所有的路径都会经过双黑节点,则所有路径到达NIL的黑色节点数量都少一但都相同,维护结束

操作:维护结束

代码:

// [N]
if (node->parent == nullptr) return;
case 3:双黑节点为黑色节点,且兄弟节点为红色

分析:

  1. 兄弟节点为红色,则根据性质3可知父亲节点一定为黑色
  2. 从父亲出发,经过兄弟的节点到达NIL的路径上的黑色节点数量较双黑节点多1,说明兄弟节点一定有孩子,且一定是两个黑色孩子
  3. 将这两个黑色孩子利用起来,通过旋转来转换成双黑节点为黑色节点,且兄弟节点为黑色的情形
  4. 经过旋转后,兄父关系颠倒,需要变色来形成原先的父黑子红结构

操作:

  1. 旋转:以父亲节点为旋转点,兄弟为旋转中心进行相应方向的旋转
  2. 变色:将兄父进行变色
  3. 继续维护双黑节点

代码:

//       |                        |                       |
//      [P]                                           [S]
//      / \       rotate(P)      / \        paint        / \
//     [N]    ==========>  [C1] [P]   ==========>  [C1] 

// / \ / \ / \ // [C1] [C2] [C2] [N] [C2] [N] Node *parent = node->parent; Node *sibling = node->sibling(); if (sibling->color == Red){ parent->left == sibling ? rotateRight(parent) : rotateLeft(parent); sibling->color = Black; parent->color = Red; this->maintainAfterRemove(node); }

case 4:双黑节点为黑色节点,且兄弟节点为黑色,兄弟没有红色孩子

分析:此时可以将兄弟染成红色,这样经过兄弟节点的路径也相较不经过双黑节点的路径到达NIL时黑色节点数量少1,因此可以将双黑节点上移到父亲节点

操作:兄弟变红,双黑上移

//       |                        |                      
//      (P)                      (P)                    
//      / \        paint         / \     maintain(P)      
//    [N] [S]    ==========>   [N]    ==========>  
Node *siblingChildren = sibling->left;
if (!siblingChildren || siblingChildren->color != Red) siblingChildren = sibling->right;
if (!siblingChildren || siblingChildren->color != Red){
    sibling->color = Red;
    this->maintainAfterRemove(parent);
}
case 5:双黑节点为黑色节点,且兄弟节点为黑色,兄弟有红色孩子

分析:

  1. 此时兄孩、兄弟、父亲三者可以通过四种不平衡类型(LL 、LR、RL、RR)进行旋转
  2. 同时将旋转后父亲位置的节点颜色染成原来父亲位置的节点的颜色,旋转后父亲位置的两个儿子都染成黑色,以此保证旋转后父亲和兄弟位置上的颜色不变
  3. 双黑节点被染黑的原父亲节点占据,补充了缺失的那一个黑色节点,维护结束

操作:

  1. 旋转:根据P-S-C构成的不平衡类型进行旋转
  2. 染色:将旋转后父亲位置的节点颜色染成原来父亲位置的节点的颜色,旋转后父亲位置的两个儿子都染成黑色
// LL型
//      |                      |                       |
//     (P)                    [S]                     (S)
//     / \    r-rotate(P)     / \                     / \
//   [S] [N]  ==========>   (P)&[N]  ==========> [C] [P]
//   /
// 
// LR型
//      |                      |                      |                       |
//     (P)                    (P)                                         (C)
//     / \    l-rotate(S)     / \    r-rotate(P)     / \        paint        / \
//   [S] [N]  ==========>    [N]  ==========>  [S] (P)&[N]  ==========> [S] [P]
//     \                    /
//                     [S]
// RL型和RR型同理
switch(siblingChildren->unbalancedType()){
    case 0:
        // LL
        this->rotateRight(parent);
        break;
    case 1:
        // LR
        this->rotateLeft(sibling);
        this->rotateRight(parent);
        break;
    case 2:
        // RL
        this->rotateRight(sibling);
        this->rotateLeft(parent);
        break;
    case 3:
        // RR
        this->rotateLeft(parent);
        break;
}
parent->parent->color = parent->color;
parent->parent->left->color = Black;
parent->parent->right->color = Black;

参考资料

OIwiki 红黑树 https://oi-wiki.org/ds/rbtree/

B站 红黑树的定义和插入 https://www.bilibili.com/video/BV1Xm421x7Lg

B站 红黑树的删除 https://www.bilibili.com/video/BV16m421u7Tb

完整代码

#include 
#include 
#include     
#include   

template> 
class RBTree{
private:
    enum Color{Black,Red};
    struct Node{
        struct Node *left = nullptr;
        struct Node *right = nullptr;
        struct Node *parent = nullptr;
        Color color;
        Key key;
        Value value;
        Node(Key key, Value value,struct Node* parent)
            : left(nullptr),right(nullptr), parent(parent), 
            color(Red), key(key), value(value) {}

        // 获得兄弟节点
        inline struct Node* sibling() const noexcept{
            return this->parent->left == this ? this->parent->right : this->parent->left;
        }

        // 获得叔叔节点
        inline struct Node* uncle() const noexcept{
            return this->parent->sibling();
        }
        
        // 获得祖父节点
        inline struct Node* grandParent() const noexcept{
            return this->parent->parent;
        }

        // 判断当前不平衡类型(LL,LR,RL,RR)-> (0,1,2,3)
        inline size_t unbalancedType() const noexcept{
            size_t type = 0;
            type |= (this->parent->left == this ? 0 : 1);
            type |= (this->grandParent()->left == this->parent ? 0 : 2);
            return type;
        }
    };
    Compare compare;
    size_t size;
    Node *root;

    void rotateLeft(Node *node);
    void rotateRight(Node *node);
    Node*& findByKey(Key key,Node** parent=nullptr);
    void maintainAfterInsert(Node *node);
    void maintainAfterRemove(Node *node);

public:
    RBTree(){
        this->compare = Compare();
        this->size = 0;
        this->root = nullptr;
    }
    int insert(Key key, Value value);
    int remove(Key key);
    void traversal(int type);
};

/**
 * @brief  左旋操作
 * @param  node 旋转点     
 * @return void         
 */
template 
void RBTree::rotateLeft(Node *node){
    //     |                         |
    //     L       l-rotate(L)       R     
    //    / \      ==========>      / \
    //   T1  R                     L   T3
    //      / \                   / \
    //    T2   T3               T1   T2  
    Node *temp = node->right;
    // 需要修改三对关系
    // 第一对
    node->right = temp->left;
    if(temp->left != nullptr){
        temp->left->parent = node;
    }

    // 第二对
    temp->parent = node->parent;
    if(node->parent == nullptr){
        this->root = temp;
    }else if(node == node->parent->left){
        node->parent->left = temp;
    }else{
        node->parent->right = temp;
    }

    // 第三对
    temp->left = node;
    node->parent = temp;
}

/**
 * @brief  右旋操作
 * @param  node 旋转点
 * @return void
 */
template 
void RBTree::rotateRight(Node *node){
    //     |                         |
    //     R       r-rotate(R)       L     
    //    / \      ==========>      / \
    //   L  T3                     T1  R
    //  / \                           / \
    // T1  T2                        T2  T3  
    Node* temp = node->left;
    // 需要修改三对关系
    // 第一对
    node->left = temp->right;
    if(temp->right != nullptr){
        temp->right->parent = node;
    }

    // 第二对
    temp->parent = node->parent;
    if(node->parent == nullptr){
        this->root = temp;
    }else if(node->parent->left == node){
        node->parent->left = temp;
    }else{
        node->parent->right = temp;
    }

    // 第三对
    temp->right = node;
    node->parent = temp;
}

/**
 * @brief  查找节点,返回结果的引用
 * @param  key 查询键值
 * @param  parent 不为空则保存查询节点的父亲作为返回值的一部分
 * @return Node*& 返回查询结果的引用
 */
template 
typename RBTree::Node*& RBTree::findByKey(Key key,Node** parent){
    Node **cur = &this->root;
    while (*cur){
        if (key == (*cur)->key) break;
        if(parent) *parent = *cur;
        cur = this->compare(key,(*cur)->key) ? &(*cur)->left : &(*cur)->right;
    }
    return *cur;
}

/**
 * @brief  维持插入节点后红黑树的平衡
 * @param  node 需要进行维护的节点
 * @return void
 */
template 
void RBTree::maintainAfterInsert(Node* node){
    // case 1:插入的节点是根节点
    // 分析:性质2要求根节点为黑
    // 操作:直接将节点染黑
    // (N) ===> [N]
    if(node->parent == nullptr){
        node->color = Black;
        return ;
    }
    
    // case 2:
    // 插入的节点的父亲是黑色
    // 分析:不会违反任何一条性质
    // 操作:直接返回
    // [P]
    //  |  
    // 
    if(node->parent->color == Black) return ;
   
    Node* uncle = node->uncle();
    Node *parent = node->parent;
    Node *grandParent = node->grandParent();
    // case 3: 插入的节点是父亲是红色,且叔叔不存在或为黑色
    // 分析:
    // 1. 由于父亲是红色,则其一定也有父亲,即祖父节点存在,否则父亲是根节点会违反性质2。
    // 2. 祖父节点存在且一定为黑色,否则违反性质3
    // 注意:理论上对新插入的节点而言,其叔叔要么不存在,要么为红,之所以还是要判断叔叔是否为黑色,是因为后续可能继续维护新插入节点的祖先节点,而祖先节点的叔叔可以为黑
    // 操作:
    // 1. 旋转:根据节点、父亲、祖父三代构成的不平衡类型(LL LR RL RR)进行同AVL树维护平衡时一样的旋转操作
    // 2. 染色:此时父亲会取代祖父的位置,需要继承祖父的黑色,而祖父需要染红
    // 此处举例LR型,本质上是先转换成LL型,再按照LL型进行旋转,RL型同理
    //      |                         |                         |                      |
    //     [G]                       [G]                                           [N]
    //     / \       l-rotate(P)     / \       r-rotate(G)     / \      paint         / \
    //   

U?|[U] ==========> U?|[U] ==========>

[G] ==========>

// \ / \ \ //

U?|[U] U?|[U] if(!uncle || uncle->color == Black){ switch (node->unbalancedType()){ case 0: // LL this->rotateRight(grandParent); break; case 1: // LR this->rotateLeft(parent); this->rotateRight(grandParent); break; case 2: // RL this->rotateRight(parent); this->rotateLeft(grandParent); break; case 3: // RR this->rotateLeft(grandParent); break; } // 祖父染红 grandParent->color = Red; // 无论是什么类型,最后祖父的父亲肯定是顶替其原来位置的节点,染红 grandParent->parent->color = Black; return ; } // case 4: 插入的节点是父亲是红色,且叔叔也为红色 // 分析: // 1. 此时违反的是性质3,因此可以将祖父节点的黑色下放到两个红孩节点,并且祖父染红,这样以祖父为根的红黑树不会冲突 // 2. 将祖父染红可能会违反性质2或性质3,和插入新节点可能会引起的不平衡的情况相同,因此继续维护祖父节点 // 操作: // 1.红色下放:将父亲和叔叔染黑,祖父染红 // 2.维护祖父:继续维护祖父节点 // | | // [G] // / \ paint / \ maintain(G) //

==========> [P] [U] ==========> // \ \ // parent->color = Black; uncle->color =Black; grandParent->color = Red; this->maintainAfterInsert(grandParent); } /** * @brief 插入或更新节点 * @param key 新节点的key * @param value 新节点的value * @return int 0表示插入成功,1表示节点已存在,更新value */ template int RBTree::insert(Key key, Value value){ Node *parent = nullptr; Node *&node = this->findByKey(key,&parent); if(node){ node->value = value; return 1; } node = new Node(key,value,parent); this->size++; this->maintainAfterInsert(node); return 0; } /** * @brief 维持删除节点后红黑树的平衡 * @param node 需要进行维护的节点 * @return void */ template void RBTree::maintainAfterRemove(Node *node) { // 需要执行删除节点后的维护说明被删除的节点是一个没有孩子的黑色节点,这时候删除了该节点会导致性质4(黑路同)被破坏 // 为了解决这个问题,我们可以引入一个双黑节点的概念,其可以有两种理解方式: // 1.经过双黑节点的路径的黑色节点数量额外加一 // 2.经过双黑节点的路径相较其他不经过双黑节点的路径,黑色节点数量少一 // case 1:双黑节点是红色节点 // 分析:可以将节点染黑来弥补经过双黑节点少掉的一个黑色节点,而由于此时的双黑节点一定有两个孩子(此时双黑节点是由黑色节点上移,其左右子树都必须含有黑色节点来保证性质4) // 操作:将双黑节点染黑,维护结束 // | | // ===> [N] if (node->color == Red){ node->color = Black; return; } // case 2:双黑节点为根节点 // 分析:此时所有的路径都会经过双黑节点,则所有路径到达NIL的黑色节点数量都少一但都相同,维护结束 // 操作:维护结束 // [N] if (node->parent == nullptr) return; // case 3:双黑节点为黑色节点,且兄弟节点为红色 // 分析: // 1. 兄弟节点为红色,则根据性质3可知父亲节点一定为黑色 // 2. 从父亲出发,经过兄弟的节点到达NIL的路径上的黑色节点数量较双黑节点多1,说明兄弟节点一定有孩子,且一定是两个黑色孩子 // 3. 将这两个黑色孩子利用起来,通过旋转来转换成双黑节点为黑色节点,且兄弟节点为黑色的情形 // 4. 经过旋转后,兄父关系颠倒,需要变色来形成原先的父黑子红结构 // 操作: // 1. 旋转:以父亲节点为旋转点,兄弟为旋转中心进行相应方向的旋转 // 2. 变色:将兄父进行变色 // 3. 继续维护双黑节点 // | | | // [P] [S] // / \ rotate(P) / \ paint / \ // [N] ==========> [C1] [P] ==========> [C1]

// / \ / \ / \ // [C1] [C2] [C2] [N] [C2] [N] Node *parent = node->parent; Node *sibling = node->sibling(); if (sibling->color == Red){ parent->left == sibling ? rotateRight(parent) : rotateLeft(parent); sibling->color = Black; parent->color = Red; this->maintainAfterRemove(node); return ; } // case 4:双黑节点为黑色节点,且兄弟节点为黑色,兄弟没有红色孩子 // 分析:此时可以将兄弟染成红色,这样经过兄弟节点的路径也相较不经过双黑节点的路径到达NIL时黑色节点数量少1,因此可以将双黑节点上移到父亲节点 // 操作:兄弟变红,双黑上移 // | | // (P) (P) // / \ paint / \ maintain(P) // [N] [S] ==========> [N] ==========> Node *siblingChildren = sibling->left; if (!siblingChildren || siblingChildren->color != Red) siblingChildren = sibling->right; if (!siblingChildren || siblingChildren->color != Red){ sibling->color = Red; this->maintainAfterRemove(parent); return ; } // case 5:双黑节点为黑色节点,且兄弟节点为黑色,兄弟有红色孩子 // 分析: // 1. 此时兄孩、兄弟、父亲三者可以通过四种不平衡类型(LL 、LR、RL、RR)进行旋转 // 2. 同时将旋转后父亲位置的节点颜色染成原来父亲位置的节点的颜色,旋转后父亲位置的两个儿子都染成黑色,以此保证旋转后父亲和兄弟位置上的颜色不变 // 3. 双黑节点被染黑的原父亲节点占据,补充了缺失的那一个黑色节点,维护结束 // 操作: // 1. 旋转:根据P-S-C构成的不平衡类型进行旋转 // 2. 染色:将旋转后父亲位置的节点颜色染成原来父亲位置的节点的颜色,旋转后父亲位置的两个儿子都染成黑色 // LL型 // | | | // (P) [S] (S) // / \ r-rotate(P) / \ / \ // [S] [N] ==========> (P)&[N] ==========> [C] [P] // / // // LR型 // | | | | // (P) (P) (C) // / \ l-rotate(S) / \ r-rotate(P) / \ paint / \ // [S] [N] ==========> [N] ==========> [S] (P)&[N] ==========> [S] [P] // \ / // [S] // RL型和RR型同理 switch(siblingChildren->unbalancedType()){ case 0: // LL this->rotateRight(parent); break; case 1: // LR this->rotateLeft(sibling); this->rotateRight(parent); break; case 2: // RL this->rotateRight(sibling); this->rotateLeft(parent); break; case 3: // RR this->rotateLeft(parent); break; } parent->parent->color = parent->color; parent->parent->left->color = Black; parent->parent->right->color = Black; } /** * @brief 删除节点(若存在) * @param key 需要删除节点的key * @return int 0表示删除成功,1表示节点不存在,删除失败 */ template int RBTree::remove(Key key){ Node* node = this->findByKey(key,nullptr); if(!node) return 1; // case 1:被删除节点有2个孩子 // 操作:找到被删除节点的直接后继(或直接前驱),交换两个节点的键值对,并将要删除的节点变成直接后继(或直接前驱),由于直接后继(或直接前驱)至多有一个有一个孩子,因此将被删除节点有2个孩子的情形转换成了被删除节点有0个或1个孩子的情形 // 注:直接前驱指的是中序遍历中该节点的前一个元素,直接后继指的是中序遍历中该节点的后一个元素 if(node->left && node->right){ Node *temp = node; node = node->right; while(node->left) node = node->left; temp->key = node->key; temp->value = node->value; } Node *parent = node->parent; Node *child = node->left ? node->left : node->right; // case 2:被删除节点有1个孩子 // 分析:在红黑树中,若一个节点仅有一个孩子,则必然是父黑子红的结构,因此可以直接将孩子提升到父亲的位置,并染黑即可 // 操作:将唯一的孩子提升到父亲的位置,并染黑 if(child){ child->color = Black; } // case 3:被删除节点有0个孩子,且被删除节点为红色 // 操作:直接删除即可 // case 4:被删除节点有0个孩子,且被删除节点为黑色 // 分析:这是最为复杂的情形,在正式删除该节点前,需要对该节点进行删除节点后的维护 // 操作:维护被删除的节点 else if(node->color == Black){ this->maintainAfterRemove(node); } // 重建关系,正式删除 if(parent) parent->left == node ? parent->left = child : parent->right = child; else this->root = child; if(child) child->parent = parent; delete node; this->size--; return 0; } /** * @brief 前序、中序、后序遍历 * @param type 遍历类型 1->前序 2->中序 3->后序 * @return void */ template void RBTree::traversal(int type){ if(this->root==nullptr) return ; std::stack> st; st.push({this->root,0}); while(!st.empty()){ std::pair& node = st.top(); node.second++; if(node.second == type ) std::cout << node.first->key << (node.first->color==Red ? "R":"B") << " "; switch(node.second){ case 1: if(node.first->left) st.push({node.first->left,0}); break; case 2: if(node.first->right) st.push({node.first->right,0}); break; case 3: st.pop(); break; } } std::cout << std::endl; }

你可能感兴趣的:(数据结构,算法)