红黑树(4万字文章超详细,只为一个目的)

news2024/11/24 1:24:42

我写这篇文章的主要目的其次才是积累知识,主要是因为我想打一个同学的脸.

事情是这样的.我现在中学嘛,我们班上有一个同学他学了红黑树啊,就一副"不可一世"的样子.在我面前炫耀啊.当时我就想立马打他的脸,可是我有没有实力,所以才学习红黑树学到深夜,写了篇博客.言归正传,我们现在开始.

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

性质

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

  1. 节点为红色或黑色
  2. NIL 节点(空叶子节点)为黑色
  3. 红色节点的子节点为黑色
  4. 从根节点到 NIL 节点的每条路径上的黑色节点数量相同

注:部分资料中还加入了第五条性质,即根节点必须为黑色,这条性质要求完成插入操作后若根节点为红色则将其染黑,但由于将根节点染黑的操作也可以延迟至删除操作时进行,因此,该条性质并非必须满足。

结构

红黑树类的定义

template <typename Key, typename Value, typename Compare = std::less<Key>>
class RBTreeMap {
  // 排序函数
  Compare compare = Compare();

  // 节点结构体
  struct Node {
    ...
  };

  // 根节点指针
  Node* root = nullptr;
  // 记录红黑树中当前的节点个数
  size_t count = 0;
}

节点维护的信息

IdentifierTypeDescription
leftNode*左子节点指针
rightNode*右子节点指针
parentNode*父节点指针
colorenum { BLACK, RED }颜色枚举
keyKey节点键值,具有唯一性和可排序性
valueValue节点内储存的值

注:由于本文提供的代码示例中使用 std::share_ptr 进行内存管理,对此不熟悉的读者可以将下文中所有的 NodePtr 和 ConstNodePtr 理解为裸指针 Node*。但在实现删除操作时若使用 Node* 作为节点引用需注意应手动释放内存以避免内存泄漏,该操作在使用 std::shared_ptr 作为节点引用的示例代码中并未体现。

过程

注:由于红黑树是由 B 树衍生而来(发明时的最初的名字 symmetric binary B-tree 足以证明这点),并非直接由平衡二叉树外加限制条件推导而来,插入操作的后续维护和删除操作的后续维护中部分对操作的解释作用仅是帮助理解,并不能将其作为该操作的原理推导和证明。

旋转操作

旋转操作是多数平衡树能够维持平衡的关键,它能在不改变一棵合法 BST 中序遍历结果的情况下改变局部节点的深度。

如上图,从左图到右图的过程被称为左旋,左旋操作会使得  \alpha子树上结点的深度均减 1,使  \beta子树上结点的深度均加 1,而  \delta子树上节点的深度则不变。从右图到左图的过程被称为右旋,右旋是左旋的镜像操作。

这里给出红黑树中节点的左旋操作的示例代码:

void rotateLeft(ConstNodePtr node) {
  // clang-format off
  //     |                       |
  //     N                       S
  //    / \     l-rotate(N)     / \
  //   L   S    ==========>    N   R
  //      / \                 / \
  //     M   R               L   M
  assert(node != nullptr && node->right != nullptr);
  // clang-format on
  NodePtr parent = node->parent;
  Direction direction = node->direction();

  NodePtr successor = node->right;
  node->right = successor->left;
  successor->left = node;

  // 以下的操作用于维护各个节点的`parent`指针
  // `Direction`的定义以及`maintainRelationship`
  // 的实现请参照文章末尾的完整示例代码
  maintainRelationship(node);
  maintainRelationship(successor);

  switch (direction) {
    case Direction::ROOT:
      this->root = successor;
      break;
    case Direction::LEFT:
      parent->left = successor;
      break;
    case Direction::RIGHT:
      parent->right = successor;
      break;
  }

  successor->parent = parent;
}

注:代码中的 successor 并非平衡树中的后继节点,而是表示取代原本节点的新节点,由于在图示中 replacement 的简称 R 会与右子节点的简称 R 冲突,因此此处使用 successor 避免歧义。

插入操作

红黑树的插入操作与普通的 BST 类似,对于红黑树来说,新插入的节点初始为红色,完成插入后需根据插入节点及相关节点的状态进行修正以满足上文提到的四条性质。

插入后的平衡维护

Case 1

该树原先为空,插入第一个节点后不需要进行修正。

Case 2

当前的节点的父节点为黑色且为根节点,这时性质已经满足,不需要进行修正。

Case 3

当前节点 N 的父节点 P 是为根节点且为红色,将其染为黑色即可,此时性质也已满足,不需要进一步修正。

// clang-format off
// Case 3: Parent is root and is RED
//   Paint parent to BLACK.
//    <P>         [P]
//     |   ====>   |
//    <N>         <N>
//   p.s.
//    `<X>` is a RED node;
//    `[X]` is a BLACK node (or NIL);
//    `{X}` is either a RED node or a BLACK node;
// clang-format on
assert(node->parent->isRed());
node->parent->color = Node::BLACK;
return;

Case 4

当前节点 N 的父节点 P 和叔节点 U 均为红色,此时 P 包含了一个红色子节点,违反了红黑树的性质,需要进行重新染色。由于在当前节点 N 之前该树是一棵合法的红黑树,根据性质 4 可以确定 N 的祖父节点 G 一定是黑色,这时只要后续操作可以保证以 G 为根节点的子树在不违反性质 4 的情况下再递归维护祖父节点 G 以保证性质 3 即可。

因此,这种情况的维护需要:

  1. 将 P,U 节点染黑,将 G 节点染红(可以保证每条路径上黑色节点个数不发生改变)。
  2. 递归维护 G 节点(因为不确定 G 的父节点的状态,递归维护可以确保性质 3 成立)。
// clang-format off
// Case 4: Both parent and uncle are RED
//   Paint parent and uncle to BLACK;
//   Paint grandparent to RED.
//        [G]             <G>
//        / \             / \
//      <P> <U>  ====>  [P] [U]
//      /               /
//    <N>             <N>
// clang-format on
assert(node->parent->isRed());
node->parent->color = Node::BLACK;
node->uncle()->color = Node::BLACK;
node->grandParent()->color = Node::RED;
maintainAfterInsert(node->grandParent());
return;

 

Case 5

当前节点 N 与父节点 P 的方向相反(即 N 节点为右子节点且父节点为左子节点,或 N 节点为左子节点且父节点为右子节点。类似 AVL 树中 LR 和 RL 的情况)。根据性质 4,若 N 为新插入节点,U 则为 NIL 黑色节点,否则为普通黑色节点。

该种情况无法直接进行维护,需要通过旋转操作将子树结构调整为 Case 6 的初始状态并进入 Case 6 进行后续维护。

// clang-format off
// Case 5: Current node is the opposite direction as parent
//   Step 1. If node is a LEFT child, perform l-rotate to parent;
//           If node is a RIGHT child, perform r-rotate to parent.
//   Step 2. Goto Case 6.
//      [G]                 [G]
//      / \    rotate(P)    / \
//    <P> [U]  ========>  <N> [U]
//      \                 /
//      <N>             <P>
// clang-format on

// Step 1: Rotation
NodePtr parent = node->parent;
if (node->direction() == Direction::LEFT) {
  rotateRight(node->parent);
} else /* node->direction() == Direction::RIGHT */ {
  rotateLeft(node->parent);
}
node = parent;
// Step 2: vvv

Case 6

当前节点 N 与父节点 P 的方向相同(即 N 节点为右子节点且父节点为右子节点,或 N 节点为左子节点且父节点为右子节点。类似 AVL 树中 LL 和 RR 的情况)。根据性质 4,若 N 为新插入节点,U 则为 NIL 黑色节点,否则为普通黑色节点。

在这种情况下,若想在不改变结构的情况下使得子树满足性质 3,则需将 G 染成红色,将 P 染成黑色。但若这样维护的话则性质 4 被打破,且无法保证在 G 节点的父节点上性质 3 是否成立。而选择通过旋转改变子树结构后再进行重新染色即可同时满足性质 3 和 4。

因此,这种情况的维护需要:

  1. 若 N 为左子节点则左旋祖父节点 G,否则右旋祖父节点 G.(该操作使得旋转过后 P - N 这条路径上的黑色节点个数比 P - G - U 这条路径上少 1,暂时打破性质 4)。
  2. 重新染色,将 P 染黑,将 N 染红,同时满足了性质 3 和 4。
    // clang-format off
    // Case 6: Current node is the same direction as parent
    //   Step 1. If node is a LEFT child, perform r-rotate to grandparent;
    //           If node is a RIGHT child, perform l-rotate to grandparent.
    //   Step 2. Paint parent (before rotate) to BLACK;
    //           Paint grandparent (before rotate) to RED.
    //        [G]                 <P>               [P]
    //        / \    rotate(G)    / \    repaint    / \
    //      <P> [U]  ========>  <N> [G]  ======>  <N> <G>
    //      /                         \                 \
    //    <N>                         [U]               [U]
    // clang-format on
    assert(node->grandParent() != nullptr);
    
    // Step 1
    if (node->parent->direction() == Direction::LEFT) {
      rotateRight(node->grandParent());
    } else {
      rotateLeft(node->grandParent());
    }
    
    // Step 2
    node->parent->color = Node::BLACK;
    node->sibling()->color = Node::RED;
    
    return;

    删除操作

    红黑树的删除操作情况繁多,较为复杂。这部分内容主要通过代码示例来进行讲解。大多数红黑树的实现选择将节点的删除以及删除之后的维护写在同一个函数或逻辑块中(例如 Wikipedia 给出的 代码示例,linux 内核中的 rbtree 以及 GNU libstdc++ 中的 std::_Rb_tree 都使用了类似的写法)。笔者则认为这种实现方式并不利于对算法本身的理解,因此,本文给出的示例代码参考了 OpenJDK 中 TreeMap 的实现,将删除操作本身与删除后的平衡维护操作解耦成两个独立的函数,并对这两部分的逻辑单独进行分析。

    Case 0

    若待删除节点为根节点的话,直接删除即可,这里不将其算作删除操作的 3 种基本情况中。

    Case 1

    若待删除节点 N 既有左子节点又有右子节点,则需找到它的前驱或后继节点进行替换(仅替换数据,不改变节点颜色和内部引用关系),则后续操作中只需要将后继节点删除即可。这部分操作与普通 BST 完全相同,在此不再过多赘述。

    注:这里选择的前驱或后继节点保证不会是一个既有非 NIL 左子节点又有非 NIL 右子节点的节点。这里拿后继节点进行简单说明:若该节点包含非空左子节点,则该节点并非是 N 节点右子树上键值最小的节点,与后继节点的性质矛盾,因此后继节点的左子节点必须为 NIL。

// clang-format off
// Case 1: If the node is strictly internal
//   Step 1. Find the successor S with the smallest key
//           and its parent P on the right subtree.
//   Step 2. Swap the data (key and value) of S and N,
//           S is the node that will be deleted in place of N.
//   Step 3. N = S, goto Case 2, 3
//     |                    |
//     N                    S
//    / \                  / \
//   L  ..   swap(N, S)   L  ..
//       |   =========>       |
//       P                    P
//      / \                  / \
//     S  ..                N  ..
// clang-format on

// Step 1
NodePtr successor = node->right;
NodePtr parent = node;
while (successor->left != nullptr) {
  parent = successor;
  successor = parent->left;
}
// Step 2
swapNode(node, successor);
maintainRelationship(parent);
// Step 3: vvv

Case 2

待删除节点为叶子节点,若该节点为红色,直接删除即可,删除后仍能保证红黑树的 4 条性质。若为黑色,删除后性质 4 被打破,需要重新进行维护。

注:由于维护操作不会改变待删除节点的任何结构和数据,因此此处的代码示例中为了实现方便起见选择先进行维护,再解引用相关节点。

// clang-format off
// Case 2: Current node is a leaf
//   Step 1. Unlink and remove it.
//   Step 2. If N is BLACK, maintain N;
//           If N is RED, do nothing.
// clang-format on
// The maintain operation won't change the node itself,
//  so we can perform maintain operation before unlink the node.
if (node->isBlack()) {
  maintainAfterRemove(node);
}
if (node->direction() == Direction::LEFT) {
  node->parent->left = nullptr;
} else /* node->direction() == Direction::RIGHT */ {
  node->parent->right = nullptr;
}

 

Case 3

待删除节点有且仅有一个非 NIL 子节点,若待删除节点为红色,直接使用其子节点 S 替换即可;若为黑色,则直接使用子节点 S 替代会打破性质 4,需要在使用 S 替代后判断 S 的颜色,若为红色,则将其染黑后即可满足性质 4,否则需要进行维护才可以满足性质 4。

// Case 3: Current node has a single left or right child
//   Step 1. Replace N with its child
//   Step 2. If N is BLACK, maintain N
NodePtr parent = node->parent;
NodePtr replacement = (node->left != nullptr ? node->left : node->right);

switch (node->direction()) {
  case Direction::ROOT:
    this->root = replacement;
    break;
  case Direction::LEFT:
    parent->left = replacement;
    break;
  case Direction::RIGHT:
    parent->right = replacement;
    break;
}

if (!node->isRoot()) {
  replacement->parent = parent;
}

if (node->isBlack()) {
  if (replacement->isRed()) {
    replacement->color = Node::BLACK;
  } else {
    maintainAfterRemove(replacement);
  }
}

删除后的平衡维护

Case 1

兄弟节点 (sibling node) S 为红色,则父节点 P 和侄节点 (nephew node) C 和 D 必为黑色(否则违反性质 3)。与插入后维护操作的 Case 5 类似,这种情况下无法通过直接的旋转或染色操作使其满足所有性质,因此通过前置操作优先保证部分结构满足性质,再进行后续维护即可。

这种情况的维护需要:

  1. 若待删除节点 N 为左子节点,左旋 P; 若为右子节点,右旋 P。
  2. 将 S 染黑,P 染红(保证 S 节点的父节点满足性质 4)。
  3. 此时只需根据结构对以当前 P 节点为根的子树进行维护即可(无需再考虑旋转染色后的 S 和 D 节点)。
// clang-format off

// Case 1: Sibling is RED, parent and nephews must be BLACK
//   Step 1. If N is a left child, left rotate P;
//           If N is a right child, right rotate P.
//   Step 2. Paint S to BLACK, P to RED
//   Step 3. Goto Case 2, 3, 4, 5
//      [P]                   <S>               [S]
//      / \    l-rotate(P)    / \    repaint    / \
//    [N] <S>  ==========>  [P] [D]  ======>  <P> [D]
//        / \               / \               / \
//      [C] [D]           [N] [C]           [N] [C]
// clang-format on
ConstNodePtr parent = node->parent;
assert(parent != nullptr && parent->isBlack());
assert(sibling->left != nullptr && sibling->left->isBlack());
assert(sibling->right != nullptr && sibling->right->isBlack());
// Step 1
rotateSameDirection(node->parent, direction);
// Step 2
sibling->color = Node::BLACK;
parent->color = Node::RED;
// Update sibling after rotation
sibling = node->sibling();
// Step 3: vvv

 

Case 2

兄弟节点 S 和侄节点 C, D 均为黑色,父节点 P 为红色。此时只需将 S 染黑,将 P 染红即可满足性质 3 和 4。

// clang-format off
// Case 2: Sibling and nephews are BLACK, parent is RED
//   Swap the color of P and S
//      <P>             [P]
//      / \             / \
//    [N] [S]  ====>  [N] <S>
//        / \             / \
//      [C] [D]         [C] [D]
// clang-format on
sibling->color = Node::RED;
node->parent->color = Node::BLACK;
return;

Case 3

兄弟节点 S,父节点 P 以及侄节点 C, D 均为黑色。

此时也无法通过一步操作同时满足性质 3 和 4,因此选择将 S 染红,优先满足局部性质 4 的成立,再递归维护 P 节点根据上部结构进行后续维护。

// clang-format off
// Case 3: Sibling, parent and nephews are all black
//   Step 1. Paint S to RED
//   Step 2. Recursively maintain P
//      [P]             [P]
//      / \             / \
//    [N] [S]  ====>  [N] <S>
//        / \             / \
//      [C] [D]         [C] [D]
// clang-format on
sibling->color = Node::RED;
maintainAfterRemove(node->parent);
return;

Case 4

兄弟节点是黑色,且与 N 同向的侄节点 C(由于没有固定中文翻译,下文还是统一将其称作 close nephew) 为红色,与 N 反向的侄节点 D(同理,下文称作 distant nephew) 为黑色,父节点既可为红色又可为黑色。

此时同样无法通过一步操作使其满足性质,因此优先选择将其转变为 Case 5 的状态利用后续 Case 5 的维护过程进行修正。

该过程分为三步:

  1. 若 N 为左子节点,右旋 P,否则左旋 P。
  2. 将节点 C 染红,将节点 S 染黑。
  3. 此时已满足 Case 5 的条件,进入 Case 5 完成后续维护。

 

// clang-format off
// Case 4: Sibling is BLACK, close nephew is RED,
//         distant nephew is BLACK
//   Step 1. If N is a left child, right rotate P;
//           If N is a right child, left rotate P.
//   Step 2. Swap the color of close nephew and sibling
//   Step 3. Goto case 5
//                            {P}                {P}
//      {P}                   / \                / \
//      / \    r-rotate(S)  [N] <C>   repaint  [N] [C]
//    [N] [S]  ==========>        \   ======>        \
//        / \                     [S]                <S>
//      <C> [D]                     \                  \
//                                  [D]                [D]
// clang-format on

// Step 1
rotateOppositeDirection(sibling, direction);
// Step 2
closeNephew->color = Node::BLACK;
sibling->color = Node::RED;
// Update sibling and nephews after rotation
sibling = node->sibling();
closeNephew = direction == Direction::LEFT ? sibling->left : sibling->right;
distantNephew = direction == Direction::LEFT ? sibling->right : sibling->left;
// Step 3: vvv

Case 5

兄弟节点是黑色,且 close nephew 节点 C 为红色,distant nephew 节点 D 为黑色,父节点既可为红色又可为黑色。此时性质 4 无法满足,通过旋转操作使得黑色节点 S 变为该子树的根节点再进行染色即可满足性质 4。具体步骤如下:

  1. 若 N 为左子节点,左旋 P,反之右旋 P。
  2. 交换父节点 P 和兄弟节点 S 的颜色,此时性质 3 可能被打破。
  3. 将 distant nephew 节点 D 染黑,同时保证了性质 3 和 4。
// clang-format off
// Case 5: Sibling is BLACK, close nephew is BLACK,
//         distant nephew is RED
//   Step 1. If N is a left child, left rotate P;
//           If N is a right child, right rotate P.
//   Step 2. Swap the color of parent and sibling.
//   Step 3. Paint distant nephew D to BLACK.
//      {P}                   [S]               {S}
//      / \    l-rotate(P)    / \    repaint    / \
//    [N] [S]  ==========>  {P} <D>  ======>  [P] [D]
//        / \               / \               / \
//      [C] <D>           [N] [C]           [N] [C]
// clang-format on
assert(closeNephew == nullptr || closeNephew->isBlack());
assert(distantNephew->isRed());
// Step 1
rotateSameDirection(node->parent, direction);
// Step 2
sibling->color = node->parent->color;
node->parent->color = Node::BLACK;
// Step 3
distantNephew->color = Node::BLACK;
return;

红黑树与 4 阶 B 树 (2-3-4 树)的关系

 

红黑树是由德国计算机科学家 Rudolf Bayer 在 1972 年从 B 树上改进过来的,红黑树在当时被称作 "symmetric binary B-tree",因此与 B 树有众多相似之处。比如红黑树与 4 阶 B 树每个簇(对于红黑树来说一个簇是一个非 NIL 黑色节点和它的两个子节点,对 B 树来说一个簇就是一个节点)的最大容量为 3 且最小填充量均为 。因此我们甚至可以说红黑树与 4 阶 B 树 (2-3-4 树)在结构上是等价的。

对这方面内容感兴趣的可以观看 从 2-3-4 树的角度学习理解红黑树(视频) 进行学习。

虽然二者在结构上是等价的,但这并不意味这二者可以互相取代或者在所有情况下都可以互换使用。最显然的例子就是数据库的索引,由于 B 树不存在旋转操作,因此其所有节点的存储位置都是可以被确定的,这种结构对于不区分堆栈的磁盘来说显然比红黑树动态分配节点存储空间要更加合适。另外一点就是由于 B 树/B+ 树内储存的数据都是连续的,对于有着大量连续查询需求的数据库来说更加友好。而对于小数据量随机插入/查询的需求,由于 B 树的每个节点都存储了若干条记录,因此发生 cache miss 时就需要将整个节点的所有数据读入缓存中,在这些情况下 BST(红黑树,AVL,Splay 等)则反而会优与 B 树/B+ 树。对这方面内容感兴趣的读者可以去阅读一下 为什么 rust 中的 Map 使用的是 B 树而不是像其他主流语言一样使用红黑树。

红黑树在实际工程项目中的使用

由于红黑树是目前主流工业界综合效率最高的内存型平衡树,其在实际的工程项目中有着广泛的使用,这里列举几个实际的使用案例并给出相应的源码链接,以便读者进行对比学习。

Linux

源码:

  • linux/lib/rbtree.c

Linux 中的红黑树所有操作均使用循环迭代进行实现,保证效率的同时又增加了大量的注释来保证代码可读性,十分建议读者阅读学习。Linux 内核中的红黑树使用非常广泛,这里仅列举几个经典案例。

CFS 非实时任务调度

Linux 的稳定内核版本在 2.6.24 之后,使用了新的调度程序 CFS,所有非实时可运行进程都以虚拟运行时间为键值用一棵红黑树进行维护,以完成更公平高效地调度所有任务。CFS 弃用 active/expired 数组和动态计算优先级,不再跟踪任务的睡眠时间和区别是否交互任务,而是在调度中采用基于时间计算键值的红黑树来选取下一个任务,根据所有任务占用 CPU 时间的状态来确定调度任务优先级。

epoll

epoll 全称 event poll,是 Linux 内核实现 IO 多路复用 (IO multiplexing) 的一个实现,是原先 poll/select 的改进版。Linux 中 epoll 的实现选择使用红黑树来储存文件描述符。

Nginx

源码:

  • nginx/src/core/ngx_rbtree.h
  • nginx/src/core/ngx_rbtree.c

nginx 中的用户态定时器是通过红黑树实现的。在 nginx 中,所有 timer 节点都由一棵红黑树进行维护,在 worker 进程的每一次循环中都会调用 ngx_process_events_and_timers 函数,在该函数中就会调用处理定时器的函数 ngx_event_expire_timers,每次该函数都不断的从红黑树中取出时间值最小的,查看他们是否已经超时,然后执行他们的函数,直到取出的节点的时间没有超时为止。

关于 nginx 中红黑树的源码分析公开资源很多,读者可以自行查找学习。

STL

源码:

  • GNU libstdc++

    • libstdc++-v3/include/bits/stl_tree.h
    • libstdc++-v3/src/c++98/tree.cc
  • LLVM libcxx

    • libcxx/include/__tree
  • Microsoft STL

    • stl/inc/xtree

大多数 STL 中的 std::map 和 std::set 的内部数据结构就是一棵红黑树(例如上面提到的这些)。不过值得注意的是,这些红黑树(包括可能有读者用过的 std::_Rb_tree) 都不是 C++ 标准,虽然部分竞赛(例如 NOIP) 并未命令禁止这类数据结构,但还是应当注意这类标准库中的非标准实现不应该在工程项目中直接使用。

由于 STL 的特殊性,其中大多数实现的代码可读性都不高,因此并不建议读者使用 STL 学习红黑树。

OpenJDK

源码:

  • java.util.TreeMap<K, V>
  • java.util.TreeSet<K, V>
  • java.util.HashMap<K, V>

JDK 中的 TreeMap 和 TreeSet 都是使用红黑树作为底层数据结构的。同时在 JDK 1.8 之后 HashMap 内部哈希表中每个表项的链表长度超过 8 时也会自动转变为红黑树以提升查找效率。

笔者认为,JDK 中的红黑树实现是主流红黑树实现中可读性最高的,本文提供的参考代码很大程度上借鉴了 JDK 中 TreeMap 的实现,因此也建议读者阅读学习 JDK 中 TreeMap 的实现。

参考代码

下面的代码是用红黑树实现的 Map,即有序不可重映射:

/**
 * @file RBTreeMap.hpp
 * @brief An RBTree-based map implementation
 * @details The map is sorted according to the natural ordering of its
 *  keys or by a {@code Compare} function provided; This implementation
 *  provides guaranteed log(n) time cost for the contains, get, insert
 *  and remove operations.
 * @author [r.ivance](https://github.com/RIvance)
 */

#ifndef RBTREE_MAP_HPP
#define RBTREE_MAP_HPP

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <stack>
#include <utility>
#include <vector>

/**
 * An RBTree-based map implementation
 * https://en.wikipedia.org/wiki/Red–black_tree
 *
 * A red–black tree (RBTree) is a kind of self-balancing binary search tree.
 * Each node stores an extra field representing "color" (RED or BLACK), used
 * to ensure that the tree remains balanced during insertions and deletions.
 *
 * In addition to the requirements imposed on a binary search tree the following
 * must be satisfied by a red–black tree:
 *
 *  1. Every node is either RED or BLACK.
 *  2. All NIL nodes (`nullptr` in this implementation) are considered BLACK.
 *  3. A RED node does not have a RED child.
 *  4. Every path from a given node to any of its descendant NIL nodes goes
 * through the same number of BLACK nodes.
 *
 * @tparam Key the type of keys maintained by this map
 * @tparam Value the type of mapped values
 * @tparam Compare the compare function
 */
template <typename Key, typename Value, typename Compare = std::less<Key> >
class RBTreeMap {
 private:
  using USize = size_t;

  Compare compare = Compare();

 public:
  struct Entry {
    Key key;
    Value value;

    bool operator==(const Entry &rhs) const noexcept {
      return this->key == rhs.key && this->value == rhs.value;
    }

    bool operator!=(const Entry &rhs) const noexcept {
      return this->key != rhs.key || this->value != rhs.value;
    }
  };

 private:
  struct Node {
    using Ptr = std::shared_ptr<Node>;
    using Provider = const std::function<Ptr(void)> &;
    using Consumer = const std::function<void(const Ptr &)> &;

    enum { RED, BLACK } color = RED;

    enum Direction { LEFT = -1, ROOT = 0, RIGHT = 1 };

    Key key;
    Value value{};

    Ptr parent = nullptr;
    Ptr left = nullptr;
    Ptr right = nullptr;

    explicit Node(Key k) : key(std::move(k)) {}

    explicit Node(Key k, Value v) : key(std::move(k)), value(std::move(v)) {}

    ~Node() = default;

    inline bool isLeaf() const noexcept {
      return this->left == nullptr && this->right == nullptr;
    }

    inline bool isRoot() const noexcept { return this->parent == nullptr; }

    inline bool isRed() const noexcept { return this->color == RED; }

    inline bool isBlack() const noexcept { return this->color == BLACK; }

    inline Direction direction() const noexcept {
      if (this->parent != nullptr) {
        if (this == this->parent->left.get()) {
          return Direction::LEFT;
        } else {
          return Direction::RIGHT;
        }
      } else {
        return Direction::ROOT;
      }
    }

    inline Ptr &sibling() const noexcept {
      assert(!this->isRoot());
      if (this->direction() == LEFT) {
        return this->parent->right;
      } else {
        return this->parent->left;
      }
    }

    inline bool hasSibling() const noexcept {
      return !this->isRoot() && this->sibling() != nullptr;
    }

    inline Ptr &uncle() const noexcept {
      assert(this->parent != nullptr);
      return parent->sibling();
    }

    inline bool hasUncle() const noexcept {
      return !this->isRoot() && this->parent->hasSibling();
    }

    inline Ptr &grandParent() const noexcept {
      assert(this->parent != nullptr);
      return this->parent->parent;
    }

    inline bool hasGrandParent() const noexcept {
      return !this->isRoot() && this->parent->parent != nullptr;
    }

    inline void release() noexcept {
      // avoid memory leak caused by circular reference
      this->parent = nullptr;
      if (this->left != nullptr) {
        this->left->release();
      }
      if (this->right != nullptr) {
        this->right->release();
      }
    }

    inline Entry entry() const { return Entry{key, value}; }

    static Ptr from(const Key &k) { return std::make_shared<Node>(Node(k)); }

    static Ptr from(const Key &k, const Value &v) {
      return std::make_shared<Node>(Node(k, v));
    }
  };

  using NodePtr = typename Node::Ptr;
  using ConstNodePtr = const NodePtr &;
  using Direction = typename Node::Direction;
  using NodeProvider = typename Node::Provider;
  using NodeConsumer = typename Node::Consumer;

  NodePtr root = nullptr;
  USize count = 0;

  using K = const Key &;
  using V = const Value &;

 public:
  using EntryList = std::vector<Entry>;
  using KeyValueConsumer = const std::function<void(K, V)> &;
  using MutKeyValueConsumer = const std::function<void(K, Value &)> &;
  using KeyValueFilter = const std::function<bool(K, V)> &;

  class NoSuchMappingException : protected std::exception {
   private:
    const char *message;

   public:
    explicit NoSuchMappingException(const char *msg) : message(msg) {}

    const char *what() const noexcept override { return message; }
  };

  RBTreeMap() noexcept = default;

  ~RBTreeMap() noexcept {
    // Unlinking circular references to avoid memory leak
    this->clear();
  }

  /**
   * Returns the number of entries in this map.
   * @return size_t
   */
  inline USize size() const noexcept { return this->count; }

  /**
   * Returns true if this collection contains no elements.
   * @return bool
   */
  inline bool empty() const noexcept { return this->count == 0; }

  /**
   * Removes all of the elements from this map.
   */
  void clear() noexcept {
    // Unlinking circular references to avoid memory leak
    if (this->root != nullptr) {
      this->root->release();
      this->root = nullptr;
    }
    this->count = 0;
  }

  /**
   * Returns the value to which the specified key is mapped; If this map
   * contains no mapping for the key, a {@code NoSuchMappingException} will
   * be thrown.
   * @param key
   * @return RBTreeMap<Key, Value>::Value
   * @throws NoSuchMappingException
   */
  Value get(K key) const {
    if (this->root == nullptr) {
      throw NoSuchMappingException("Invalid key");
    } else {
      NodePtr node = this->getNode(this->root, key);
      if (node != nullptr) {
        return node->value;
      } else {
        throw NoSuchMappingException("Invalid key");
      }
    }
  }

  /**
   * Returns the value to which the specified key is mapped; If this map
   * contains no mapping for the key, a new mapping with a default value
   * will be inserted.
   * @param key
   * @return RBTreeMap<Key, Value>::Value &
   */
  Value &getOrDefault(K key) {
    if (this->root == nullptr) {
      this->root = Node::from(key);
      this->root->color = Node::BLACK;
      this->count += 1;
      return this->root->value;
    } else {
      return this
          ->getNodeOrProvide(this->root, key,
                             [&key]() { return Node::from(key); })
          ->value;
    }
  }

  /**
   * Returns true if this map contains a mapping for the specified key.
   * @param key
   * @return bool
   */
  bool contains(K key) const {
    return this->getNode(this->root, key) != nullptr;
  }

  /**
   * Associates the specified value with the specified key in this map.
   * @param key
   * @param value
   */
  void insert(K key, V value) {
    if (this->root == nullptr) {
      this->root = Node::from(key, value);
      this->root->color = Node::BLACK;
      this->count += 1;
    } else {
      this->insert(this->root, key, value);
    }
  }

  /**
   * If the specified key is not already associated with a value, associates
   * it with the given value and returns true, else returns false.
   * @param key
   * @param value
   * @return bool
   */
  bool insertIfAbsent(K key, V value) {
    USize sizeBeforeInsertion = this->size();
    if (this->root == nullptr) {
      this->root = Node::from(key, value);
      this->root->color = Node::BLACK;
      this->count += 1;
    } else {
      this->insert(this->root, key, value, false);
    }
    return this->size() > sizeBeforeInsertion;
  }

  /**
   * If the specified key is not already associated with a value, associates
   * it with the given value and returns the value, else returns the associated
   * value.
   * @param key
   * @param value
   * @return RBTreeMap<Key, Value>::Value &
   */
  Value &getOrInsert(K key, V value) {
    if (this->root == nullptr) {
      this->root = Node::from(key, value);
      this->root->color = Node::BLACK;
      this->count += 1;
      return root->value;
    } else {
      NodePtr node = getNodeOrProvide(this->root, key,
                                      [&]() { return Node::from(key, value); });
      return node->value;
    }
  }

  Value operator[](K key) const { return this->get(key); }

  Value &operator[](K key) { return this->getOrDefault(key); }

  /**
   * Removes the mapping for a key from this map if it is present;
   * Returns true if the mapping is present else returns false
   * @param key the key of the mapping
   * @return bool
   */
  bool remove(K key) {
    if (this->root == nullptr) {
      return false;
    } else {
      return this->remove(this->root, key, [](ConstNodePtr) {});
    }
  }

  /**
   * Removes the mapping for a key from this map if it is present and returns
   * the value which is mapped to the key; If this map contains no mapping for
   * the key, a {@code NoSuchMappingException} will be thrown.
   * @param key
   * @return RBTreeMap<Key, Value>::Value
   * @throws NoSuchMappingException
   */
  Value getAndRemove(K key) {
    Value result;
    NodeConsumer action = [&](ConstNodePtr node) { result = node->value; };

    if (root == nullptr) {
      throw NoSuchMappingException("Invalid key");
    } else {
      if (remove(this->root, key, action)) {
        return result;
      } else {
        throw NoSuchMappingException("Invalid key");
      }
    }
  }

  /**
   * Gets the entry corresponding to the specified key; if no such entry
   * exists, returns the entry for the least key greater than the specified
   * key; if no such entry exists (i.e., the greatest key in the Tree is less
   * than the specified key), a {@code NoSuchMappingException} will be thrown.
   * @param key
   * @return RBTreeMap<Key, Value>::Entry
   * @throws NoSuchMappingException
   */
  Entry getCeilingEntry(K key) const {
    if (this->root == nullptr) {
      throw NoSuchMappingException("No ceiling entry in this map");
    }

    NodePtr node = this->root;

    while (node != nullptr) {
      if (key == node->key) {
        return node->entry();
      }

      if (compare(key, node->key)) {
        /* key < node->key */
        if (node->left != nullptr) {
          node = node->left;
        } else {
          return node->entry();
        }
      } else {
        /* key > node->key */
        if (node->right != nullptr) {
          node = node->right;
        } else {
          while (node->direction() == Direction::RIGHT) {
            if (node != nullptr) {
              node = node->parent;
            } else {
              throw NoSuchMappingException(
                  "No ceiling entry exists in this map");
            }
          }
          if (node->parent == nullptr) {
            throw NoSuchMappingException("No ceiling entry exists in this map");
          }
          return node->parent->entry();
        }
      }
    }

    throw NoSuchMappingException("No ceiling entry in this map");
  }

  /**
   * Gets the entry corresponding to the specified key; if no such entry exists,
   * returns the entry for the greatest key less than the specified key;
   * if no such entry exists, a {@code NoSuchMappingException} will be thrown.
   * @param key
   * @return RBTreeMap<Key, Value>::Entry
   * @throws NoSuchMappingException
   */
  Entry getFloorEntry(K key) const {
    if (this->root == nullptr) {
      throw NoSuchMappingException("No floor entry exists in this map");
    }

    NodePtr node = this->root;

    while (node != nullptr) {
      if (key == node->key) {
        return node->entry();
      }

      if (compare(key, node->key)) {
        /* key < node->key */
        if (node->left != nullptr) {
          node = node->left;
        } else {
          while (node->direction() == Direction::LEFT) {
            if (node != nullptr) {
              node = node->parent;
            } else {
              throw NoSuchMappingException("No floor entry exists in this map");
            }
          }
          if (node->parent == nullptr) {
            throw NoSuchMappingException("No floor entry exists in this map");
          }
          return node->parent->entry();
        }
      } else {
        /* key > node->key */
        if (node->right != nullptr) {
          node = node->right;
        } else {
          return node->entry();
        }
      }
    }

    throw NoSuchMappingException("No floor entry exists in this map");
  }

  /**
   * Gets the entry for the least key greater than the specified
   * key; if no such entry exists, returns the entry for the least
   * key greater than the specified key; if no such entry exists,
   * a {@code NoSuchMappingException} will be thrown.
   * @param key
   * @return RBTreeMap<Key, Value>::Entry
   * @throws NoSuchMappingException
   */
  Entry getHigherEntry(K key) {
    if (this->root == nullptr) {
      throw NoSuchMappingException("No higher entry exists in this map");
    }

    NodePtr node = this->root;

    while (node != nullptr) {
      if (compare(key, node->key)) {
        /* key < node->key */
        if (node->left != nullptr) {
          node = node->left;
        } else {
          return node->entry();
        }
      } else {
        /* key >= node->key */
        if (node->right != nullptr) {
          node = node->right;
        } else {
          while (node->direction() == Direction::RIGHT) {
            if (node != nullptr) {
              node = node->parent;
            } else {
              throw NoSuchMappingException(
                  "No higher entry exists in this map");
            }
          }
          if (node->parent == nullptr) {
            throw NoSuchMappingException("No higher entry exists in this map");
          }
          return node->parent->entry();
        }
      }
    }

    throw NoSuchMappingException("No higher entry exists in this map");
  }

  /**
   * Returns the entry for the greatest key less than the specified key; if
   * no such entry exists (i.e., the least key in the Tree is greater than
   * the specified key), a {@code NoSuchMappingException} will be thrown.
   * @param key
   * @return RBTreeMap<Key, Value>::Entry
   * @throws NoSuchMappingException
   */
  Entry getLowerEntry(K key) const {
    if (this->root == nullptr) {
      throw NoSuchMappingException("No lower entry exists in this map");
    }

    NodePtr node = this->root;

    while (node != nullptr) {
      if (compare(key, node->key) || key == node->key) {
        /* key <= node->key */
        if (node->left != nullptr) {
          node = node->left;
        } else {
          while (node->direction() == Direction::LEFT) {
            if (node != nullptr) {
              node = node->parent;
            } else {
              throw NoSuchMappingException("No lower entry exists in this map");
            }
          }
          if (node->parent == nullptr) {
            throw NoSuchMappingException("No lower entry exists in this map");
          }
          return node->parent->entry();
        }
      } else {
        /* key > node->key */
        if (node->right != nullptr) {
          node = node->right;
        } else {
          return node->entry();
        }
      }
    }

    throw NoSuchMappingException("No lower entry exists in this map");
  }

  /**
   * Remove all entries that satisfy the filter condition.
   * @param filter
   */
  void removeAll(KeyValueFilter filter) {
    std::vector<Key> keys;
    this->inorderTraversal([&](ConstNodePtr node) {
      if (filter(node->key, node->value)) {
        keys.push_back(node->key);
      }
    });
    for (const Key &key : keys) {
      this->remove(key);
    }
  }

  /**
   * Performs the given action for each key and value entry in this map.
   * The value is immutable for the action.
   * @param action
   */
  void forEach(KeyValueConsumer action) const {
    this->inorderTraversal(
        [&](ConstNodePtr node) { action(node->key, node->value); });
  }

  /**
   * Performs the given action for each key and value entry in this map.
   * The value is mutable for the action.
   * @param action
   */
  void forEachMut(MutKeyValueConsumer action) {
    this->inorderTraversal(
        [&](ConstNodePtr node) { action(node->key, node->value); });
  }

  /**
   * Returns a list containing all of the entries in this map.
   * @return RBTreeMap<Key, Value>::EntryList
   */
  EntryList toEntryList() const {
    EntryList entryList;
    this->inorderTraversal(
        [&](ConstNodePtr node) { entryList.push_back(node->entry()); });
    return entryList;
  }

 private:
  static void maintainRelationship(ConstNodePtr node) {
    if (node->left != nullptr) {
      node->left->parent = node;
    }
    if (node->right != nullptr) {
      node->right->parent = node;
    }
  }

  static void swapNode(NodePtr &lhs, NodePtr &rhs) {
    std::swap(lhs->key, rhs->key);
    std::swap(lhs->value, rhs->value);
    std::swap(lhs, rhs);
  }

  void rotateLeft(ConstNodePtr node) {
    // clang-format off
    //     |                       |
    //     N                       S
    //    / \     l-rotate(N)     / \
    //   L   S    ==========>    N   R
    //      / \                 / \
    //     M   R               L   M
    assert(node != nullptr && node->right != nullptr);
    // clang-format on
    NodePtr parent = node->parent;
    Direction direction = node->direction();

    NodePtr successor = node->right;
    node->right = successor->left;
    successor->left = node;

    maintainRelationship(node);
    maintainRelationship(successor);

    switch (direction) {
      case Direction::ROOT:
        this->root = successor;
        break;
      case Direction::LEFT:
        parent->left = successor;
        break;
      case Direction::RIGHT:
        parent->right = successor;
        break;
    }

    successor->parent = parent;
  }

  void rotateRight(ConstNodePtr node) {
    // clang-format off
    //       |                   |
    //       N                   S
    //      / \   r-rotate(N)   / \
    //     S   R  ==========>  L   N
    //    / \                     / \
    //   L   M                   M   R
    assert(node != nullptr && node->left != nullptr);
    // clang-format on

    NodePtr parent = node->parent;
    Direction direction = node->direction();

    NodePtr successor = node->left;
    node->left = successor->right;
    successor->right = node;

    maintainRelationship(node);
    maintainRelationship(successor);

    switch (direction) {
      case Direction::ROOT:
        this->root = successor;
        break;
      case Direction::LEFT:
        parent->left = successor;
        break;
      case Direction::RIGHT:
        parent->right = successor;
        break;
    }

    successor->parent = parent;
  }

  inline void rotateSameDirection(ConstNodePtr node, Direction direction) {
    assert(direction != Direction::ROOT);
    if (direction == Direction::LEFT) {
      rotateLeft(node);
    } else {
      rotateRight(node);
    }
  }

  inline void rotateOppositeDirection(ConstNodePtr node, Direction direction) {
    assert(direction != Direction::ROOT);
    if (direction == Direction::LEFT) {
      rotateRight(node);
    } else {
      rotateLeft(node);
    }
  }

  void maintainAfterInsert(NodePtr node) {
    assert(node != nullptr);

    if (node->isRoot()) {
      // Case 1: Current node is root (RED)
      //  No need to fix.
      assert(node->isRed());
      return;
    }

    if (node->parent->isBlack()) {
      // Case 2: Parent is BLACK
      //  No need to fix.
      return;
    }

    if (node->parent->isRoot()) {
      // clang-format off
      // Case 3: Parent is root and is RED
      //   Paint parent to BLACK.
      //    <P>         [P]
      //     |   ====>   |
      //    <N>         <N>
      //   p.s.
      //    `<X>` is a RED node;
      //    `[X]` is a BLACK node (or NIL);
      //    `{X}` is either a RED node or a BLACK node;
      // clang-format on
      assert(node->parent->isRed());
      node->parent->color = Node::BLACK;
      return;
    }

    if (node->hasUncle() && node->uncle()->isRed()) {
      // clang-format off
      // Case 4: Both parent and uncle are RED
      //   Paint parent and uncle to BLACK;
      //   Paint grandparent to RED.
      //        [G]             <G>
      //        / \             / \
      //      <P> <U>  ====>  [P] [U]
      //      /               /
      //    <N>             <N>
      // clang-format on
      assert(node->parent->isRed());
      node->parent->color = Node::BLACK;
      node->uncle()->color = Node::BLACK;
      node->grandParent()->color = Node::RED;
      maintainAfterInsert(node->grandParent());
      return;
    }

    if (!node->hasUncle() || node->uncle()->isBlack()) {
      // Case 5 & 6: Parent is RED and Uncle is BLACK
      //   p.s. NIL nodes are also considered BLACK
      assert(!node->isRoot());

      if (node->direction() != node->parent->direction()) {
        // clang-format off
        // Case 5: Current node is the opposite direction as parent
        //   Step 1. If node is a LEFT child, perform l-rotate to parent;
        //           If node is a RIGHT child, perform r-rotate to parent.
        //   Step 2. Goto Case 6.
        //      [G]                 [G]
        //      / \    rotate(P)    / \
        //    <P> [U]  ========>  <N> [U]
        //      \                 /
        //      <N>             <P>
        // clang-format on

        // Step 1: Rotation
        NodePtr parent = node->parent;
        if (node->direction() == Direction::LEFT) {
          rotateRight(node->parent);
        } else /* node->direction() == Direction::RIGHT */ {
          rotateLeft(node->parent);
        }
        node = parent;
        // Step 2: vvv
      }

      // clang-format off
      // Case 6: Current node is the same direction as parent
      //   Step 1. If node is a LEFT child, perform r-rotate to grandparent;
      //           If node is a RIGHT child, perform l-rotate to grandparent.
      //   Step 2. Paint parent (before rotate) to BLACK;
      //           Paint grandparent (before rotate) to RED.
      //        [G]                 <P>               [P]
      //        / \    rotate(G)    / \    repaint    / \
      //      <P> [U]  ========>  <N> [G]  ======>  <N> <G>
      //      /                         \                 \
      //    <N>                         [U]               [U]
      // clang-format on

      assert(node->grandParent() != nullptr);

      // Step 1
      if (node->parent->direction() == Direction::LEFT) {
        rotateRight(node->grandParent());
      } else {
        rotateLeft(node->grandParent());
      }

      // Step 2
      node->parent->color = Node::BLACK;
      node->sibling()->color = Node::RED;

      return;
    }
  }

  NodePtr getNodeOrProvide(NodePtr &node, K key, NodeProvider provide) {
    assert(node != nullptr);

    if (key == node->key) {
      return node;
    }

    assert(key != node->key);

    NodePtr result;

    if (compare(key, node->key)) {
      /* key < node->key */
      if (node->left == nullptr) {
        result = node->left = provide();
        node->left->parent = node;
        maintainAfterInsert(node->left);
        this->count += 1;
      } else {
        result = getNodeOrProvide(node->left, key, provide);
      }
    } else {
      /* key > node->key */
      if (node->right == nullptr) {
        result = node->right = provide();
        node->right->parent = node;
        maintainAfterInsert(node->right);
        this->count += 1;
      } else {
        result = getNodeOrProvide(node->right, key, provide);
      }
    }

    return result;
  }

  NodePtr getNode(ConstNodePtr node, K key) const {
    assert(node != nullptr);

    if (key == node->key) {
      return node;
    }

    if (compare(key, node->key)) {
      /* key < node->key */
      return node->left == nullptr ? nullptr : getNode(node->left, key);
    } else {
      /* key > node->key */
      return node->right == nullptr ? nullptr : getNode(node->right, key);
    }
  }

  void insert(NodePtr &node, K key, V value, bool replace = true) {
    assert(node != nullptr);

    if (key == node->key) {
      if (replace) {
        node->value = value;
      }
      return;
    }

    assert(key != node->key);

    if (compare(key, node->key)) {
      /* key < node->key */
      if (node->left == nullptr) {
        node->left = Node::from(key, value);
        node->left->parent = node;
        maintainAfterInsert(node->left);
        this->count += 1;
      } else {
        insert(node->left, key, value, replace);
      }
    } else {
      /* key > node->key */
      if (node->right == nullptr) {
        node->right = Node::from(key, value);
        node->right->parent = node;
        maintainAfterInsert(node->right);
        this->count += 1;
      } else {
        insert(node->right, key, value, replace);
      }
    }
  }

  void maintainAfterRemove(ConstNodePtr node) {
    if (node->isRoot()) {
      return;
    }

    assert(node->isBlack() && node->hasSibling());

    Direction direction = node->direction();

    NodePtr sibling = node->sibling();
    if (sibling->isRed()) {
      // clang-format off
      // Case 1: Sibling is RED, parent and nephews must be BLACK
      //   Step 1. If N is a left child, left rotate P;
      //           If N is a right child, right rotate P.
      //   Step 2. Paint S to BLACK, P to RED
      //   Step 3. Goto Case 2, 3, 4, 5
      //      [P]                   <S>               [S]
      //      / \    l-rotate(P)    / \    repaint    / \
      //    [N] <S>  ==========>  [P] [D]  ======>  <P> [D]
      //        / \               / \               / \
      //      [C] [D]           [N] [C]           [N] [C]
      // clang-format on
      ConstNodePtr parent = node->parent;
      assert(parent != nullptr && parent->isBlack());
      assert(sibling->left != nullptr && sibling->left->isBlack());
      assert(sibling->right != nullptr && sibling->right->isBlack());
      // Step 1
      rotateSameDirection(node->parent, direction);
      // Step 2
      sibling->color = Node::BLACK;
      parent->color = Node::RED;
      // Update sibling after rotation
      sibling = node->sibling();
      // Step 3: vvv
    }

    NodePtr closeNephew =
        direction == Direction::LEFT ? sibling->left : sibling->right;
    NodePtr distantNephew =
        direction == Direction::LEFT ? sibling->right : sibling->left;

    bool closeNephewIsBlack = closeNephew == nullptr || closeNephew->isBlack();
    bool distantNephewIsBlack =
        distantNephew == nullptr || distantNephew->isBlack();

    assert(sibling->isBlack());

    if (closeNephewIsBlack && distantNephewIsBlack) {
      if (node->parent->isRed()) {
        // clang-format off
        // Case 2: Sibling and nephews are BLACK, parent is RED
        //   Swap the color of P and S
        //      <P>             [P]
        //      / \             / \
        //    [N] [S]  ====>  [N] <S>
        //        / \             / \
        //      [C] [D]         [C] [D]
        // clang-format on
        sibling->color = Node::RED;
        node->parent->color = Node::BLACK;
        return;
      } else {
        // clang-format off
        // Case 3: Sibling, parent and nephews are all black
        //   Step 1. Paint S to RED
        //   Step 2. Recursively maintain P
        //      [P]             [P]
        //      / \             / \
        //    [N] [S]  ====>  [N] <S>
        //        / \             / \
        //      [C] [D]         [C] [D]
        // clang-format on
        sibling->color = Node::RED;
        maintainAfterRemove(node->parent);
        return;
      }
    } else {
      if (closeNephew != nullptr && closeNephew->isRed()) {
        // clang-format off
        // Case 4: Sibling is BLACK, close nephew is RED,
        //         distant nephew is BLACK
        //   Step 1. If N is a left child, right rotate P;
        //           If N is a right child, left rotate P.
        //   Step 2. Swap the color of close nephew and sibling
        //   Step 3. Goto case 5
        //                            {P}                {P}
        //      {P}                   / \                / \
        //      / \    r-rotate(S)  [N] <C>   repaint  [N] [C]
        //    [N] [S]  ==========>        \   ======>        \
        //        / \                     [S]                <S>
        //      <C> [D]                     \                  \
        //                                  [D]                [D]
        // clang-format on

        // Step 1
        rotateOppositeDirection(sibling, direction);
        // Step 2
        closeNephew->color = Node::BLACK;
        sibling->color = Node::RED;
        // Update sibling and nephews after rotation
        sibling = node->sibling();
        closeNephew =
            direction == Direction::LEFT ? sibling->left : sibling->right;
        distantNephew =
            direction == Direction::LEFT ? sibling->right : sibling->left;
        // Step 3: vvv
      }

      // clang-format off
      // Case 5: Sibling is BLACK, close nephew is BLACK,
      //         distant nephew is RED
      //      {P}                   [S]
      //      / \    l-rotate(P)    / \
      //    [N] [S]  ==========>  {P} <D>
      //        / \               / \
      //      [C] <D>           [N] [C]
      // clang-format on
      assert(closeNephew == nullptr || closeNephew->isBlack());
      assert(distantNephew->isRed());
      // Step 1
      rotateSameDirection(node->parent, direction);
      // Step 2
      sibling->color = node->parent->color;
      node->parent->color = Node::BLACK;
      if (distantNephew != nullptr) {
        distantNephew->color = Node::BLACK;
      }
      return;
    }
  }

  bool remove(NodePtr node, K key, NodeConsumer action) {
    assert(node != nullptr);

    if (key != node->key) {
      if (compare(key, node->key)) {
        /* key < node->key */
        NodePtr &left = node->left;
        if (left != nullptr && remove(left, key, action)) {
          maintainRelationship(node);
          return true;
        } else {
          return false;
        }
      } else {
        /* key > node->key */
        NodePtr &right = node->right;
        if (right != nullptr && remove(right, key, action)) {
          maintainRelationship(node);
          return true;
        } else {
          return false;
        }
      }
    }

    assert(key == node->key);
    action(node);

    if (this->size() == 1) {
      // Current node is the only node of the tree
      this->clear();
      return true;
    }

    if (node->left != nullptr && node->right != nullptr) {
      // clang-format off
      // Case 1: If the node is strictly internal
      //   Step 1. Find the successor S with the smallest key
      //           and its parent P on the right subtree.
      //   Step 2. Swap the data (key and value) of S and N,
      //           S is the node that will be deleted in place of N.
      //   Step 3. N = S, goto Case 2, 3
      //     |                    |
      //     N                    S
      //    / \                  / \
      //   L  ..   swap(N, S)   L  ..
      //       |   =========>       |
      //       P                    P
      //      / \                  / \
      //     S  ..                N  ..
      // clang-format on

      // Step 1
      NodePtr successor = node->right;
      NodePtr parent = node;
      while (successor->left != nullptr) {
        parent = successor;
        successor = parent->left;
      }
      // Step 2
      swapNode(node, successor);
      maintainRelationship(parent);
      // Step 3: vvv
    }

    if (node->isLeaf()) {
      // Current node must not be the root
      assert(node->parent != nullptr);

      // Case 2: Current node is a leaf
      //   Step 1. Unlink and remove it.
      //   Step 2. If N is BLACK, maintain N;
      //           If N is RED, do nothing.

      // The maintain operation won't change the node itself,
      //  so we can perform maintain operation before unlink the node.
      if (node->isBlack()) {
        maintainAfterRemove(node);
      }
      if (node->direction() == Direction::LEFT) {
        node->parent->left = nullptr;
      } else /* node->direction() == Direction::RIGHT */ {
        node->parent->right = nullptr;
      }
    } else /* !node->isLeaf() */ {
      assert(node->left == nullptr || node->right == nullptr);
      // Case 3: Current node has a single left or right child
      //   Step 1. Replace N with its child
      //   Step 2. If N is BLACK, maintain N
      NodePtr parent = node->parent;
      NodePtr replacement = (node->left != nullptr ? node->left : node->right);
      switch (node->direction()) {
        case Direction::ROOT:
          this->root = replacement;
          break;
        case Direction::LEFT:
          parent->left = replacement;
          break;
        case Direction::RIGHT:
          parent->right = replacement;
          break;
      }

      if (!node->isRoot()) {
        replacement->parent = parent;
      }

      if (node->isBlack()) {
        if (replacement->isRed()) {
          replacement->color = Node::BLACK;
        } else {
          maintainAfterRemove(replacement);
        }
      }
    }

    this->count -= 1;
    return true;
  }

  void inorderTraversal(NodeConsumer action) const {
    if (this->root == nullptr) {
      return;
    }

    std::stack<NodePtr> stack;
    NodePtr node = this->root;

    while (node != nullptr || !stack.empty()) {
      while (node != nullptr) {
        stack.push(node);
        node = node->left;
      }
      if (!stack.empty()) {
        node = stack.top();
        stack.pop();
        action(node);
        node = node->right;
      }
    }
  }
};

#endif  // RBTREE_MAP_HPP

打了一个晚上终于打出来的(虽然最后一段die码是复制的).

可是我发现了一个问题,我是先画好的图它忽然今天就传不上去了,这是我很奇怪的,请各位大佬指教一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度解析:会用Excel,还有必要学Python吗?

前言 某站上有个问题&#xff1a; 我都会用Excel了&#xff0c;还有必要学Python吗&#xff1f; &#xff08;文末送读者福利&#xff09; 这个问题大概率可以说明问这个问题的这位同学目前还没有遇到非Python不可的场景&#xff0c;之所以产生了学Python的念头是因为这两年P…

今日分享:文字转语音软件哪个好

如今已经进入到了一个短视频时代&#xff0c;大家饭后或者闲暇时都会通过刷视频来消磨时间&#xff0c;而部分小伙伴看到一些有趣的内容&#xff0c;也想试着自己制作一下。但众所周知&#xff0c;视频拍摄容易&#xff0c;后期剪辑制作确实异常困难的&#xff0c;有许多道工序…

从boot引导到loader引导完整运行

此文针对该文章对loader引导进行了完善后的完整运行过程。&#xff08;具体细节请参见下文&#xff09; boot引导升级&#xff0c;成功引导运行loader_What’smean的博客-CSDN博客boot引导升级&#xff0c;成功引导运行loaderhttps://blog.csdn.net/weixin_42492218/article/d…

【数据结构】11道LeetCode链表OJ练习

文章目录1. 移除链表元素2. 反转链表3. 链表的中间节点4. 链表中倒数第k个节点5. 合并两个有序链表6. 链表分割7. 链表的回文结构8. 相交链表9. 环形链表10. 环形链表II11. 复制带随机指针的链表补充内容&#xff1a;浅谈顺序表和链表的区别1. 移除链表元素 移除链表元素OJ链接…

扫雷游戏优化详解——c语言实现

文章目录 一、扫雷游戏的简单认识与解释 二、扫雷游戏的代码及思路实现 一、扫雷游戏的思路 1、菜单打印 2、创建扫雷区 3、初始化雷区 4、打印雷区 5、布置雷区 6、排雷 三、扫雷游戏代码的整合 game.h game.c test.c 标题&#xff1a;猜数字小游戏 作者&#xff1a;Ggggg…

你适合考PMP还是软考?两者的区别是否清楚,分别能给你带来什么价值

PMP与软考之间有什么区别&#xff0c;应该考哪个更适合自己&#xff1f; 下面从9个方面给大家简单的介绍做一个对比&#xff0c;希望能帮上忙~ 软考和PMP哪个更适合自己&#xff1f; 01 考试介绍 PMP&#xff1a;PMP是项目管理专业人士资格认证&#xff0c;由美国项目管理协…

腾讯云相同配置8核16G的云服务器和轻量服务器该如何选择?

很多个人或者中小企业用户在选择云服务器的时候&#xff0c;已经确认配置的情况下&#xff0c;去选购的时候发现有出现了轻量应用服务器&#xff0c;那么轻量应用服务器和云服务器又有哪些区别&#xff0c;价格为啥又有那么大的差别&#xff0c;该如何选择呢&#xff1f; 从上边…

SSM框架整合详细教程

目录 1. 什么是SSM&#xff1f; 2. SSM整合的时候容器之间如何访问 3. SSM下面的开发步骤 4. SSM整合时候时容易混乱的知识点 1. 什么是SSM&#xff1f; SSM是对三个框架名字的简写&#xff0c;其中第一个S指的是SpringMVC,第二个S指的是Spring&#xff0c;第三个M指的是M…

项目搭建(七)爱心代码❤网站部署(静态网站)

爱心代码❤网站部署&#xff08;静态网站&#xff09;一、环境基础二、修改Tomcat启动配置三、放置静态网站四、启动Tomcat一、环境基础 如果你已经部署了Apache-Tomcat&#xff0c;恭喜你&#xff0c;你已经完成90%的部署工作 如果没有tomcat&#xff0c;那你先部署tomcat吧 …

4_单机优化(确定性算法,优化框架)

优化框架机器学习的优化框架正则化经验风险最小化优化算法的收敛速率假设条件凸函数定义强凸函数定义光滑函数定义优化算法的分类机器学习的优化框架 正则化经验风险最小化 有监督的机器学习问题&#xff1a; 假设输入输出数据 Sn{(xi,yi);i1,...,n}S_n \left\{(x_i, y_i);…

C++与C语言中的字符串

目录 1、关于c语言中的字符串 &#xff08;1&#xff09;c语言中字符串与字符指针 &#xff08;2&#xff09;字符串结尾 2、关于c中的字符串string &#xff08;1&#xff09;从本质上了解string &#xff08;2&#xff09;c中的字符串转换与关联 &#xff08;3&#x…

【MySQL入门指北】MySQL备份及恢复

MySQL备份及恢复 文章目录MySQL备份及恢复1.Percona 介绍2.安装Percona 需要的 MySQL 包3.安装percona-xtrabackup4.完全备份流程5.完全恢复流程6.增量备份流程7.差异备份8.差异恢复流程9.记录的导入和导出10.mysqldumpbinlog11.MySQL恢复数据12.二进制日志恢复13.误删除库的问…

基于51单片机的室内温度可燃气体检测报警系统Proteus仿真

资料编号&#xff1a;133 下面是相关功能视频演示&#xff1a; 133-基于51单片机的室内温度可燃气体检测报警系统Proteus仿真&#xff08;源码仿真全套资料&#xff09;功能介绍&#xff1a; 采用51单片机作为主控&#xff0c;LCD1602显示当前温度和可燃气体浓度&#xff0c;…

Netty源码阅读(1)之——客户端源码梗概

目录 准备 开始 NioSocketChannel 的初始化过程 指定 初始化 关于unsafe属性&#xff1a; 关于pipeline的初始化 小结 EventLoopGroup初始化 小结 channel的注册过程 handler的注册过程 客户端连接 总结 准备 源码阅读基于4.1.84.Final版本。从github下载netty项目…

WordPress设置浏览器切换标签网站动态标题

我们在逛别人网站的时候&#xff0c;经常看到&#xff0c;有些网站当我们离开该页面浏览其他页面的时候&#xff0c;离开的页面标题上会显示比如&#xff1a;“你别走吖 Σ(っ Д ;)っ”这样的字样&#xff0c;当我们点回来的时候页面上面的标题又变成了“你又回来啦&#xff0…

[附源码]计算机毕业设计JAVAjsp学生档案管理系统

[附源码]计算机毕业设计JAVAjsp学生档案管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM myb…

前端基础向--从项目入手封装公共组件

本文就从 “详情卡片” 业务组件的封装的几个阶段来说明我在编写公共组件的设计思路。 1. 阶段一&#xff1a;基础需求 假设我们现在有这样一个需求&#xff1a;需要满足显示产品的详细信息&#xff1b;需要可以根据不同分辨率适配不同的显示方式&#xff08;2列&#xff0c;…

【Linux】进程通信 | 管道

今天让我们来认识如何使用管道来进行进程间通信 文章目录1.何为管道&#xff1f;1.1 管道是进程间通信的一种方式1.2 进程通信1.3 管道分类2.匿名管道2.0 康康源码2.1 创建2.2 父子通信完整代码2.3 等待写入等待读取等待源码中的体现2.4 控制多个子进程2.5 命令行 |3.命名管道3…

linux无界面手敲命令笔记

0 Ubuntu相关命令简介 1. 文件及目录操作命令 pwd&#xff1a;显示用户当前所处的目录 ls&#xff1a;列出目录下的文件清单 cd&#xff1a;改变当前目录cd … 返回上一级目cd / 进入根目录不加参数或参数为“~”&#xff0c;默认切换到用户主目录 mkdir&#xff1a;建立目录 …

Ant Design表单之labelCol 和wrapperCol的实际开发笔记

目录 前言 一、labelCol和wrapperCol是什么 二、布局的栅格化 1.布局的栅格化系统的工作原理 三、栅格常用的属性 1.左右偏移 2.区块间隔 3.栅格排序 四、labelCol和wrapperCol的实际使用 总结 前言 主要是记录一下栅格布局的一些属性和labelCol、wrapperCol等。 一…