从libuv源码中学习红黑树

阅读本文你需具有知识点:html

  • 二叉查找树(js版本的简单实现能够参考二叉查找树的简单学习)
  • 准备纸和笔(本身动手画一画,这样方能真的理解)
  • 推荐你们一个好用的在线生成红黑树的网站(这个不能演示过程):Virtual
    • 国内你们都推荐使用这个在线生成,不过打不开了,后来我找到原始网站,就把他贴在了本身的博客域名下,你们能够用一用:在线生成

一、libuv如何用红黑树?

libuv将红黑树的算法用在了signal上,咱们先回顾一下signal是怎么使用的:node

uv_signal_t signal_handle;
r = uv_signal_init(loop, &signal_handle);
CHECK(r, "uv_signal_init");

r = uv_signal_start(&signal_handle, signal_cb, SIGINT);

void signal_cb(uv_signal_t *handle, int signum) {
  printf("signal_cb: recvd CTRL+C shutting down\n");
  uv_stop(uv_default_loop()); //stops the event loop
}
复制代码

当开发者每调用一次uv_signal_start的时候,都会往生成的红黑树中插入一个节点,当调用uv_signal_stop的时候,则会删除一个节点,以下:git

static int uv__signal_start(uv_signal_t* handle,
                            uv_signal_cb signal_cb,
                            int signum,
                            int oneshot) {
  ... ...
  RB_INSERT(uv__signal_tree_s, &uv__signal_tree, handle);
  ... ...
}

static void uv__signal_stop(uv_signal_t* handle) {
  ... ...
  removed_handle = RB_REMOVE(uv__signal_tree_s, &uv__signal_tree, handle);
  ... ...
}
复制代码

libuv使用宏的形式(源码在tree.h),定义了一整套红黑树的完整实现,由于宏代码看起来比较费劲,我随意截了个图,你们能够瞄一眼:github

自己红黑树就是很难搞懂的概念,加上C语言,因此太不友好了。因而我根据C语言的实现,改写成Js版本的,下面的讲解都是基于js语言的,因此你们勿慌~算法

二、红黑树的基本概念

2.一、为何须要红黑树?

红黑树的基础是二叉搜索树,那么二叉搜索树很差吗?非要再整这么一个复杂的出来?二叉搜索树本来已是个很好的数据结构,能够快速地找到一个给定关键字的数据项,而且能够快速地插入和删除数据项。可是二叉搜索树有个很麻烦的问题,若是树中插入的是随机数据,则执行效果很好,但若是插入的是有序或者逆序的数据,那么二叉搜索树的执行速度就变得很慢。由于当插入数值有序时,二叉树就是非平衡的了,排在一条线上,其实就变成了一个链表……它的快速查找、插入和删除指定数据项的能力就丧失了。好比下图的bash

为了能以较快的时间 O(logN) 来搜索一棵树,须要保证树老是平衡的(或者至少大部分是平衡的),这就是说对树中的每一个节点在它左边的后代数目和在它右边的后代数目应该大体相等。红-黑树的就是这样的一棵平衡树,对一个要插入的数据项,插入过程当中要检查会不会破坏树的特征,若是破坏了,程序就会进行纠正,根据须要改变树的结构,从而保持树的平衡。数据结构

Tips: 有的童鞋可能会提出问题:AVL和红黑树之间,为啥红黑树用的场景更多?其实这是一个性能平衡的问题,当然AVL的节点查找比红黑树好,可是由于其严苛的条件,致使在每次插入和删除的时候须要作的运算比红黑树多。因此牺牲一点查找时间,保证每次插入和删除节点的时候性能也不会太差。函数

2.二、红黑树特征

  • 性质1:每一个节点要么是黑色,要么是红色。
  • 性质2:根节点是黑色。
  • 性质3:每一个叶子节点(NIL)是黑色。
  • 性质4:每一个红色结点的两个子结点必定都是黑色。
  • 性质5:任意一结点到每一个叶子结点的路径都包含数量相同的黑结点。

而后根据以上特征有以下推论:oop

  • 推论1:红黑树中不会出现两个红色结点相邻的情形
  • 推论2:树中最短的可能出现的路径是都是黑色结点的路径
  • 推论3:树中最长的可能出现的路径是红色结点和黑色结点交替的路径
  • 推论4:每条路径上均包含相同数目的黑色结点,因此红黑树确保没有一条路径会比其余路径长出2倍
  • 推论5:若是一个结点存在黑子结点,那么该结点确定有两个子结点

由此咱们获得节点的数据结构:性能

function Node(data) {
  this.leftNode = null
  this.rightNode = null
  this.parentNode = null
  this.data = data
  this.rbColor = RED // 初始化为红色的缘由下面有说到
}
复制代码

2.三、红黑树的修正操做

红黑树的修正都是基于旋转和变色的,由于咱们在搞懂插入一个节点以后会怎么修正以前,咱们须要读懂左旋和右旋。

2.3.一、左旋

下图是一个左旋的动画:

从图中能够看出,所谓左旋就是将要旋转的节点往左边挪动,也就是逆时针走向,完成的操做有如下三件事:

  1. 将S的左子节点赋给E的右子节点,并将E赋给S左子节点的父节点(E左子节点非空时)
  2. 将E的父节点p(非空时)赋给E的父节点,同时更新E以前的父节点的子节点为y(多是左或右,取决于当时E节点的位置)
  3. 将S的左子节点设为E,将E的父节点设为S

根据上面的介绍,用Js实现的代码以下(代码中的注释以函数的顶部注释为准):

/*************对红黑树节点x进行左旋操做 ******************/
/*
 * 左旋示意图:对节点x进行左旋
 *     p                       p
 *    /                       /
 *   x                       y
 *  / \                     / \
 * lx  y      ----->       x  ry
 *    / \                 / \
 *   ly ry               lx ly
 * 左旋作了三件事:
 * 1. 将y的左子节点赋给x的右子节点,并将x赋给y左子节点的父节点(y左子节点非空时)
 * 2. 将x的父节点p(非空时)赋给y的父节点,同时更新p的子节点为y(左或右)
 * 3. 将y的左子节点设为x,将x的父节点设为y
 ********************************************************/
rotateLeft(beRotatedNode) {
  // x节点的右节点y
  const rightNode = beRotatedNode.rightNode
  // x节点的右节点变动掉,将y的左节点赋值过来
  beRotatedNode.rightNode = rightNode.leftNode
  // 若是y节点的左节点有值,那么x将是y节点的左节点的父节点
  if (rightNode.leftNode) {
    rightNode.leftNode.parentNode = beRotatedNode
  }
  // 将x的父节点赋值给y节点的父节点
  rightNode.parentNode = beRotatedNode.parentNode
  // 若是x的父节点存在的话
  if (beRotatedNode.parentNode) {
    // 若是x节点以前是其父节点的左节点
    if (beRotatedNode === beRotatedNode.parentNode.leftNode) {
      beRotatedNode.parentNode.leftNode = rightNode
    } else {
      beRotatedNode.parentNode.rightNode = rightNode
    }
  } else {
    // 若是x的父节点为空,那么说明是根节点
    this.root = rightNode
  }

  // 3. 将y的左节点设为x,将x的父节点设为y
  rightNode.leftNode = beRotatedNode
  beRotatedNode.parentNode = rightNode
}
复制代码

2.3.二、右旋

右旋的操做与左旋相反,仍是有动画:

具体的就再也不赘述,实现的代码以下:

/*************对红黑树节点y进行右旋操做 ******************/
/*
* 右旋示意图:对节点y进行右旋
*        p                   p
*       /                   /
*      y                   x
*     / \                 / \
*    x  ry   ----->      lx  y
*   / \                     / \
* lx  rx                   rx ry
* 右旋作了三件事:
* 1. 将x的右子节点赋给y的左子节点,并将y赋给x右子节点的父节点(x右子节点非空时)
* 2. 将y的父节点p(非空时)赋给x的父节点,同时更新p的子节点为x(左或右)
* 3. 将x的右子节点设为y,将y的父节点设为x
*/
rotateRight(beRotatedNode) {
  // y节点的左节点x
  const leftNode = beRotatedNode.leftNode
  // y节点的左节点变动掉,将x的右节点赋值过来
  beRotatedNode.leftNode = leftNode.rightNode
  // 若是x节点的右节点有值,那么y将是x节点的右节点的父节点
  if (leftNode.rightNode) {
    leftNode.rightNode.parentNode = beRotatedNode
  }
  // 将y的父节点赋值给x节点的父节点
  leftNode.parentNode = beRotatedNode.parentNode
  // 若是y的父节点存在的话
  if (beRotatedNode.parentNode) {
    // 若是y节点以前是其父节点的左节点
    if (beRotatedNode === beRotatedNode.parentNode.leftNode) {
      beRotatedNode.parentNode.leftNode = leftNode
    } else {
      beRotatedNode.parentNode.rightNode = leftNode
    }
  } else {
    // 若是x的父节点为空,那么说明是根节点
    this.root = leftNode
  }

  // 3. 将x的左节点设为y,将y的父节点设为x
  leftNode.rightNode = beRotatedNode
  beRotatedNode.parentNode = leftNode
}
复制代码

三、红黑树的插入

接着咱们进入正题,了解插入以前,咱们先约定好一些术语,这样结合代码阅读的时候不至于懵。以下图:

全部节点的一些称呼都标注清楚了,后面的插入和删除的操做都是基于这些称呼的。

首先以二叉搜索树的方式插入结点,并将其着为红色。若是着为黑色,则会违背性质5,不便调整;若是着为红色,可能会违背性质2或性质4,能够经过相对简单的操做,使其恢复红黑树的性质。

将一个节点插入到红黑树中,须要执行哪些步骤呢?首先,实例化一个新节点,而后将红黑树看成一颗二叉查找树,查找适合的位置将节点插入;最后,经过"旋转和从新着色"等一系列操做来修正该树,使之从新成为一颗红黑树。

详细描述以下:

3.一、第一步

实例化新节点,代码以下:const node = new Node(data)

3.二、第二步

将红黑树看成一颗二叉查找树,查找适合节点插入的位置。这一步查找的逻辑其实很简单,总结以下:

  1. 根据节点的值,从根节点开始遍历,找到适合插入的父节点
  2. 将该插入的节点和父节点创建联系

实现代码以下:

__insert(node) {
  let root = this.root
  let parent = null
  let compareRes
  // 2. 查找插入节点适合存放的位置,并与其父节点创建联系
  while(root !== null) {
    parent = root

    compareRes = node.data - root.data
    // 若是插入的节点比当前父节点大,那么继续寻找该父节点的右节点
    if (compareRes > 0) {
      root = root.rightNode
    } else if (compareRes < 0) {
      root = root.leftNode
    } else {
      return root
    }
  }
  // 找到插入节点的父节点了,此时parent表示的就是插入节点的父节点
  node.parentNode = parent

  // 接着判断是插入到该父节点的左边仍是右边
  if (parent) {
    if (compareRes < 0) {
      parent.leftNode = node
    } else {
      parent.rightNode = node
    }
  } else {
    this.root = node
  }
  // 3. 修整整个红黑树
  this.__insert_color(this.root, node)
  return null
}
复制代码

3.三、第三步

经过一系列的旋转或着色等操做,使之从新成为一颗红黑树。

若是是第一次插入,因为原树为空,因此只会违反红黑树的特征2,因此只要把根节点涂黑便可;若是插入节点的父节点是黑色的,那不会违背红黑树的特征,什么也不须要作;

可是遇到以下三种状况时(如下统一称为场景3),咱们就要开始变色和旋转了:

  1. 插入节点的父节点和其叔叔节点均为红色的;

  2. 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点;

  3. 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。

该场景下有这么一个推论,须要记住:若是插入的父结点为红结点,那么该父结点不可能为根结点,因此插入结点老是存在祖父结点。

场景3.一、叔叔结点存在而且为红结点

从红黑树性质4能够,祖父结点确定为黑结点,由于不能够同时存在两个相连的红结点。那么此时该插入子树的红黑层数的状况是:黑红红。显然最简单的处理方式是把其改成:红黑红。

所以处理方式是:

将父节点和叔叔节点设置为黑色
将祖父节点设置为红色
把祖父设置为当前插入结点,继续调整红黑树
复制代码

场景3.二、叔叔结点不存在或为黑结点,而且插入结点的父结点是祖父结点的左子结点

单纯从插入前来看,叔叔结点非红即为叶子结点(Nil)。由于若是叔叔结点为黑结点,而父结点为红结点,那么叔叔结点所在的子树的黑色结点就比父结点所在子树的多了,这不知足红黑树的性质5。后续情景一样如此,再也不多作说明了。

前文说了,须要旋转操做时,确定一边子树的结点多了或少了,须要租或借给另外一边。插入显然是多的状况,那么把多的结点租给另外一边子树就能够了。

场景3.2.一、插入结点是其父结点的左子结点

左边两个红结点,右边不存在,那么一边一个刚恰好,而且由于为红色,确定不会破坏树的平衡。所以处理方式是:

将父节点设为黑色
将祖父节点设为红色
对祖父节点进行右旋
复制代码

场景3.2.二、插入结点是其父结点的右子结点

这种情景显然能够经过旋转将问题归一化到3.2.1小节的状况,所以处理方式以下:

对父节点进行左旋
把父节点设置为插入结点,归一化到3.2.1小节的状况后继续处理
复制代码

场景3.三、叔叔结点不存在或为黑结点,而且插入结点的父亲结点是祖父结点的右子结点

该情景对应情景3.2,只是方向反转,不作过多说明了,直接给出结论。

场景3.3.一、插入结点是其父结点的右子结点

处理方式:

将父节点设为黑色
将祖父节点设为红色
对祖父节点进行左旋
复制代码

场景3.3.二、插入结点是其父结点的右子结点

处理方式:

对父节点进行右旋
把父节点设置为插入结点,归一化到3.3.1小节的状况后继续处理
复制代码

综上所述,实现的代码以下,代码有注释,一一对应上面的全部状况:

__insert_color(root, node) {
  let parent = node.parentNode
  let gparent, uncle

  // 第一种状况和第二种状况都不会进入这个while语句,因此都是很简单的
  // 只有第三种状况才会进入,也就是父节点存在而且父节点是红色
  while(parent !== null && parent.rbColor === RED) {
    gparent = parent.parentNode
    // 若是父节点是祖父节点的左节点
    if (parent === gparent.leftNode) {
      // 取叔叔节点,也就是父节点的兄弟节点
      uncle = gparent.rightNode
      // case3.1状况:若是叔叔节点存在而且颜色是红色
      if (uncle && uncle.rbColor === RED) {
        // 一、将叔叔节点、父节点、祖父节点分别置为黑黑红
        uncle.rbColor = BLACK
        parent.rbColor = BLACK
        gparent.rbColor = RED
        // 二、将祖父节点置为当前节点
        node = gparent
        // 祖父节点变动后,父节点必然也得变动
        parent = node.parentNode
        continue
      }
      // case3.2.二、若是叔叔节点不存在或者叔叔节点是黑色,而且插入的节点是在父节点的右边
      // 由于该状况最后会归一化到case3.2.1的状况,因此须要先执行
      if (parent.rightNode === node) {
        // 一、以父节点为支点,进行左旋
        this.rotateLeft(parent)
        // 旋转以后须要从新设置
        uncle = parent
        parent = node
        node = uncle
      }
      // 上面一种状况最后要归一化到最后的这种状况case3.2.1:叔叔节点不存在或者叔叔节点是黑色的,而且插入
      // 的节点是在父节点的左边
      // 一、将父节点和祖父节点分别设置为黑红
      parent.rbColor = BLACK
      gparent.rbColor = RED
      // 二、对祖父节点进行右旋
      this.rotateRight(gparent)
      // 更新父节点
      parent = node.parentNode
    } else {
      // 父节点是祖父节点的右节点,状况和上面彻底相反

      // 取叔叔节点,也就是父节点的兄弟节点
      uncle = gparent.leftNode
      // case3.1状况:若是叔叔节点存在而且颜色是红色
      if (uncle && uncle.rbColor === RED) {
        // 一、将叔叔节点、父节点、祖父节点分别置为黑黑红
        uncle.rbColor = BLACK
        parent.rbColor = BLACK
        gparent.rbColor = RED
        // 二、将祖父节点置为当前节点
        node = gparent
        // 祖父节点变动后,父节点必然也得变动
        parent = node.parentNode
        continue
      }
      // case3.3.二、若是叔叔节点不存在或者叔叔节点是黑色,而且插入的节点是在父节点的右边
      // 由于该状况最后会归一化到case3.3.1的状况,因此须要先执行
      if (parent.leftNode === node) {
        // 一、以父节点为支点,进行右旋
        this.rotateRight(parent)
        // 旋转以后须要从新设置
        uncle = parent
        parent = node
        node = uncle
      }
      // 上面一种状况最后要归一化到最后的这种状况case3.3.1:叔叔节点不存在或者叔叔节点是黑色的,而且插入
      // 的节点是在父节点的右边
      // 一、将父节点和祖父节点分别设置为黑红
      parent.rbColor = BLACK
      gparent.rbColor = RED
      // 二、对祖父节点进行左旋
      this.rotateLeft(gparent)
      // 更新父节点
      parent = node.parentNode
    }
  }
  this.root.rbColor = BLACK
}
复制代码

四、红黑树的删除

将红黑树内的某一个节点删除。须要执行的操做依次是:首先,将红黑树看成一颗二叉查找树,将该节点从二叉查找树中删除;而后,经过"旋转和从新着色"等一系列来修正该树,使之从新成为一棵红黑树。详细描述以下:

第一步:将红黑树看成一颗二叉查找树,将节点删除。 这和"删除常规二叉查找树中删除节点的方法是同样的"。分3种状况:

① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。

② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的惟一子节点顶替它的位置。

③ 被删除节点有两个儿子。那么,先找出它的后继节点(也就是大于被删除结点的最小结点);在这里,后继节点至关于替身,在将后继节点的内容复制给"被删除节点"以后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的状况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的状况下,它的后继节点不多是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"状况① "进行处理;若只有一个儿子,则按"状况② "进行处理。

这么一说,因而咱们有了以下推论:删除操做删除的结点能够看做删除替代结点,而替代结点最后老是在树末

示意图以下:

第二步:经过"旋转和从新着色"等一系列来修正该树,使之从新成为一棵红黑树。

由于"第一步"中删除节点以后,可能会违背红黑树的特性。因此须要经过"旋转和从新着色"来修正该树,使之从新成为一棵红黑树。

4.一、第一步

根据以上罗列的状况,处理三种状况。实现代码以下:

__remove(node) {
  let replaceNode = node
  // 若是被删除的节点左右节点都不为空
  if (node.leftNode && node.rightNode) {
    // 先找出被删除节点的后继节点(大于删除结点的最小结点)来当作替换节点
    let replaceNode = node.rightNode
    while(replaceNode.leftNode) {
      replaceNode = replaceNode.leftNode
    }

    node.data = replaceNode.data
  }
  const parent = replaceNode.parentNode
  const color = replaceNode.rbColor
  // 这里有着足够的隐含信息,若是被删除节点不是左右节点都有,那么这里的child是null或者左右节点之一,
  // 若是被删除节点左右节点皆有,那么这里的是替换节点,而替换节点的左节点确定是不存在的,只能是右节点或者null
  const child = replaceNode.rightNode || replaceNode.leftNode
  if (child) {
    child.parentNode = parent
  }
  if (parent) {
    if (parent.leftNode === replaceNode) {
      parent.leftNode = child
    } else {
      parent.rightNode = child
    }
  } else {
    this.root = child
  }
  if (color === BLACK) {
    this.__remove_color(child, parent)
  }
  replaceNode = null
}
复制代码

若是恰好删除的节点是黑色,那么开始咱们的红黑树重建

4.二、第二步

红黑树的重建,这个时候只有替换的节点是黑色的时候,才须要重建,所以忽略场景1(替换结点是红色结点),直接说场景2:

场景2.一、替换结点是其父结点的左子结点

场景2.1.一、替换结点的兄弟结点是红结点

处理方式:

将兄弟设为黑色 将父节点设为红色 对父节点进行左旋,归一化到场景2.1.2.3

场景2.1.二、替换结点的兄弟结点是黑结点

该场景又根据兄弟节点的子节点的不一样,分为不一样的场景

场景2.1.2.一、替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色

将兄弟节点的颜色设为父节点的颜色 将父节点设为黑色 将兄弟节点的右节点设为黑色 对父节点进行左旋

场景2.1.2.二、替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点

将兄弟节点设为红色 将兄弟节点的左节点设为黑色 对兄弟节点进行右旋,归一化到场景2.1.2.1进行处理

场景2.1.2.三、替换结点的兄弟结点的子结点都为黑结点

将兄弟节点设为红色 把父节点做为新的替换结点 从新进行删除结点情景处理

场景2.二、替换结点是其父结点的右子结点

场景2.2.一、替换结点的兄弟结点是红结点

将兄弟节点设为黑色 将父节点设为红色 对父节点进行右旋,归一化到场景2.2.2.3

场景2.2.二、替换结点的兄弟结点是黑结点

场景2.2.2.一、替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色

将兄弟节点的颜色设为父节点的颜色 将父节点设为黑色 将兄弟节点的左节点设为黑色 对父节点进行右旋

场景2.2.2.二、替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点

将兄弟设为红色 将兄弟节点的右节点设为黑色 对兄弟节点进行左旋,归一化到场景2.2.2.1

场景2.2.2.三、替换结点的兄弟结点的子结点都为黑结点

将兄弟节点设为红色 把父节点做为新的替换结点 从新进行删除结点情景处理

完整实现代码以下:

// node表示待修正的节点,即后继节点的子节点(由于后继节点被删除了)
__remove_color(node, parent) {
  let brother
  while((node === null || node.rbColor === BLACK) && node !== this.root) {
    // 场景2.一、替换结点是其父结点的左子结点
    if (node == parent.leftNode) {
      brother = parent.rightNode // 取兄弟节点
      // 场景2.1.一、替换结点的兄弟结点是红结点
      if (brother.rbColor === RED) {
        brother.rbColor = BLACK
        parent.rbColor = RED
        this.rotateLeft(parent)
        // 归一化到场景2.1.2.3
        brother = parent.rightNode
      }
      // 场景2.1.二、替换结点的兄弟结点是黑结点
      // 场景2.1.2.三、替换结点的兄弟结点的子结点都为黑结点
      if ((brother.leftNode === null || brother.leftNode.rbColor === BLACK) && (brother.rightNode === null || brother.rightNode.rbColor === BLACK)) {
        brother.rbColor = RED
        node = parent
        parent = node.parentNode
      } else {
        // 场景2.1.2.二、替换结点的兄弟结点的右子结点为黑结点,左子结点为红结点
        if (brother.rightNode === null || brother.rightNode.rbColor === BLACK) {
          if (brother.leftNode) {
            brother.leftNode.rbColor = BLACK
          }
          brother.rbColor = RED
          // 右旋后归一化到场景2.1.2.1
          this.rotateRight(brother)
          brother = parent.rightNode
        }
        // 场景2.1.2.一、替换结点的兄弟结点的右子结点是红结点,左子结点任意颜色
        brother.rbColor = parent.rbColor
        parent.rbColor = BLACK
        if (brother.rightNode) {
          brother.rightNode.rbColor = BLACK
        }
        this.rotateLeft(parent)
        node = this.root
        break
      }
    } else {
      // 场景2.二、替换结点是其父结点的右子结点
      brother = parent.leftNode
      // 场景2.2.一、替换结点的兄弟结点是红结点
      if (brother.rbColor === RED) {
        brother.rbColor = BLACK
        parent.rbColor = RED
        // 右旋后会进入场景2.2.2.3
        this.rotateRight(parent)
        brother = parent.leftNode
      }

      // 场景2.2.二、替换结点的兄弟结点是黑结点
      // 场景2.2.2.三、替换结点的兄弟结点的子结点都为黑结点
      if ((brother.leftNode === null || brother.leftNode.rbColor === BLACK) && (brother.rightNode === null || brother.rightNode.rbColor === BLACK)) {
        brother.rbColor = RED
        node = parent
        parent = node.parentNode
      } else {
        // 场景2.2.2.二、替换结点的兄弟结点的左子结点为黑结点,右子结点为红结点
        if (brother.leftNode === null || brother.leftNode.rbColor === BLACK) {
          if (brother.rightNode) {
            brother.rightNode.rbColor = BLACK
          }
          brother.rbColor = RED
          // 左旋后归一化到场景2.2.2.1
          this.rotateLeft(brother)
          brother = parent.leftNode
        }

        // 场景2.2.2.一、替换结点的兄弟结点的左子结点是红结点,右子结点任意颜色
        brother.rbColor = parent.rbColor
        parent.rbColor = BLACK

        if (brother.leftNode) {
          brother.leftNode.rbColor = BLACK
        }
        this.rotateRight(parent)
        node = this.root
        break
      }
    }
  }
  if (node) {
    node.rbColor = BLACK
  }
}
复制代码

参考

  1. 30张图带你完全理解红黑树
  2. 红黑树(二)之 C语言的实现
相关文章
相关标签/搜索