红黑树

红黑树是一种自平衡二叉搜索树,保证插入、删除、查找操作的平均时间复杂度为O(logn)。其节点包含颜色属性,有5条性质确保平衡。插入操作分为三步,新节点默认为红色,插入后可能需要调整以满足性质。删除操作分为四步,涉及‘双黑’修正,以保持性质。
摘要由CSDN通过智能技术生成

红黑树是一种自平衡二叉搜索树,与AVL树类似,在其上进行的插入、删除、查找操作的平均时间复杂度均为 O ( l o g   n ) O(log\,n) O(logn)

但与AVL树不同的是,红黑树的平衡不是非常严格的平衡(即左右子树高度差不超过1),它牺牲了部分平衡性来换取了插入、删除时的少量旋转操作(最多3次)。

与普通二叉搜索树的区别

节点

  • 颜色:相比于普通的二叉搜索树,红黑树的节点增加了一个颜色属性:黑色或红色,这也是红黑树名字的由来。

  • 叶子节点:红黑树特别区分了中间节点叶子节点。在二叉搜索树中,叶子节点与中间节点一般不做区分,两者的区别仅在于有没有子节点(叶节点子节点为NULL,视为没有子节点)。虽然在红黑树中也可以这样,但是为了方便解释与实现,一般对叶子节点进行了区分:叶子节点不存储数据,只是充当树在此结束的标志。

  • 父指针:因为红黑树的特殊性,在进行平衡调整时,一般都会涉及到父节点与兄弟节点(具体请参见后文),所以在每个节点中都存储了指向其父节点的指针(叶子节点可能例外,参见后文)。

所以红黑树的节点定义如下:

// 枚举节点颜色:只有黑色与红色
enum COLOR {
    BLACK, RED };

class Node {
   
  public:
    Node* parent; // 父节点
    Node* left;   // 左子节点
    Node* right;  // 右子节点
    COLOR color;  // 节点颜色
    int val;      // 节点的值
};

5条性质

在普通二叉搜索树的基础上,红黑树的定义增加了以下5条性质:

  1. 节点是黑色或者红色。(即树中任意节点非黑即红,没有其它颜色)
  2. 根节点是黑色。
  3. 所有的叶子节点是黑色。
  4. 每个红色节点一定有两个黑色的节点。(即有父子关系的两个节点不能均为红色)
  5. 从任一节点出发,到其每个叶子节点的所有简单路径上有相同数量的黑节点。(这个黑节点数量也被称为黑高

基于这5个性质,红黑树能够保证从根节点到叶子节点最长深度不会超过最短深度的2倍,从而保证了红黑树的平衡。

因为最长路径即为红黑交替出现,而最短路径即为全部为黑节点。又由于性质5,所以这两条路径上的黑色节点数目一定相同,根节点与叶节点均为黑色,最长路径上多出的红色节点数目一定不会多于黑色节点的数目。

也正因为红黑树平衡性是通过黑高来进行维护的,所以在其节点上不用存储树的高度。

树的结构

一般情况下,红黑树有以下三种结构(参考《算法导论》):

  • 所有叶子节点为NULL,在其它节点上存储节点的黑高。如图:
    NULL.png

  • 叶子节点由一个节点实例来代表,并且根节点的父节点也指向此节点,如图:
    Tril.png

  • 每个分支都有一个单独的叶节点实例,如图:
    leaf.png

本文选取第二种结构

操作

对于二叉搜索树的操作基本上也就是三种:增加删除查找

红黑树上也是一样,其中查找操作与普通的二叉搜索树上相同,此处便不再赘述。下文主要以最麻烦的插入与删除来进行说明。

在此之前,先说明一下文中代码所用的红黑树结构(这里只说明了类中的数据,函数声明与定义请参见文末源码):

class RedBlackTree {
   
  private:
    // 枚举节点颜色:只有黑色与红色
    enum COLOR {
    BLACK, RED };

    class Node {
   
      public:
        Node* parent; // 父节点
        Node* left;   // 左子节点
        Node* right;  // 右子节点
        COLOR color;  // 节点颜色
        int val;      // 节点的值
    };

    // 叶节点,只有一个实例,其中的值没有意义(默认为0)、其父、左子、右子节点均为nullptr
    Node* leaf;
    // 树的根节点,通过此指针可以得到整颗树
    Node* root;
    // 双黑节点的父节点,只有在删除操作后进行双黑修正时值才有意义,其余情况下均为nullptr
    Node* dBlackParent;
}

插入

先从较为简单的插入操作说起。

总的来说,分为三步:

  • 找到应该插入到树中的哪个位置
  • 创建新的红色节点并将其插入到树中。
  • 对树进行调整(包括染色与旋转),使其满足红黑树的定义(5条性质)。

红黑树本质上也是一颗二叉搜索树,所以它的插入操作与二叉搜索树一样:先找到应该插入的位置,然后插入。这一部分不必多说。

不同的是,因为红黑树增加了5条性质,在插入后有可能会违反其中一条或几条性质,所以在红黑树上的插入操作多了一步调整。

首先,如果一颗红黑树不为空时,那么性质1、2、3则会自然满足,不用去多加考虑。所以我们只需要保证性质4与性质5便可。

为什么要默认插入红色节点呢?

因为性质5更为复杂(因为只要有某一节点不满足,那么其父节点也不满足,从此节点向上到根节点,路径上的所有节点都会不满足),如果破坏了性质5,再进行恢复所付出的代价也会更大,所以我们在插入时尽量先保证性质5不会被破坏。因此,在进行插入时,我们将新的节点默认为红色。这样,不论此节点被插入到何处,都不会破坏性质5(性质5只与黑色节点有关)。

5种情况

默认为红色的新节点插入到树中后,会出现下面5种情况(为方便讨论,在此假设当前讨论节点为N(在插入后第一次讨论时,N代表新插入的节点)、N的父节点为P、P的兄弟节点为U、N的祖父节点为G(即P的父节点)):

情形1:N就是根节点。

这时我们只需要将此节点重绘为黑色即可。这样,树上的所有节点的黑高都增加了1,性质5得到满足。

void RedBlackTree::InsertCase1(Node* root) {
   
    // 树为空,直接返回
    if (!root) return;
    // 当前节点为根节点,染黑当前节点并返回
    if (root->parent == leaf) {
   
        root->color = BLACK;
        return;
    }
    // 否则进入情形2判断
    InsertCase2(root);
}

情形2:P为黑色。

此时性质4没有被破坏,性质5因为插入的是红色节点,也未被破坏。所以此种情况不用调整

void RedBlackTree::InsertCase2(Node* root) {
   
    if (root->parent->color == BLACK) return;
    // 否则进入情形3判断
    else
        InsertCase3(root)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值