// SPDX-License-Identifier: BSD-2-Clause // Copyright CM4all GmbH // author: Max Kellermann #pragma once #include // for std::any_of() #include #include #include // for std::exchange() struct RedBlackTreeNode { RedBlackTreeNode *parent; enum class Direction : std::size_t { LEFT, RIGHT }; std::array children; enum class Color { HEAD, BLACK, RED }; Color color; constexpr RedBlackTreeNode() noexcept = default; struct Head {}; explicit constexpr RedBlackTreeNode(Head) noexcept :children({}), color(Color::HEAD) {} RedBlackTreeNode(const RedBlackTreeNode &) = delete; RedBlackTreeNode &operator=(const RedBlackTreeNode &) = delete; constexpr void Init(Color _color) noexcept { children = {}; color = _color; } [[nodiscard]] constexpr bool IsHead() const noexcept { return color == Color::HEAD; } [[nodiscard]] constexpr bool IsRoot() const noexcept { assert(!IsHead()); return parent->IsHead(); } [[nodiscard]] static constexpr Direction OtherDirection(Direction direction) noexcept { return static_cast(static_cast(direction) ^ 1); } [[nodiscard]] constexpr RedBlackTreeNode *GetChild(Direction direction) const noexcept { return children[static_cast(direction)]; } [[nodiscard]] constexpr RedBlackTreeNode *GetLeft() const noexcept { return GetChild(Direction::LEFT); } [[nodiscard]] constexpr RedBlackTreeNode *GetRight() const noexcept { return GetChild(Direction::RIGHT); } [[nodiscard]] constexpr RedBlackTreeNode *GetOtherChild(Direction direction) const noexcept { return GetChild(OtherDirection(direction)); } /** * Set a new child and return the old one. */ constexpr auto *SetChild(Direction direction, RedBlackTreeNode *child) noexcept { auto *old = std::exchange(children[static_cast(direction)], child); if (child != nullptr) child->parent = this; return old; } constexpr auto *SetChild(Direction direction, RedBlackTreeNode &child) noexcept { auto *old = std::exchange(children[static_cast(direction)], &child); child.parent = this; return old; } constexpr auto *SetOtherChild(Direction direction, RedBlackTreeNode *child) noexcept { return SetChild(OtherDirection(direction), child); } [[nodiscard]] constexpr Direction GetChildDirection(const RedBlackTreeNode &child) const noexcept { assert(child.parent == this); assert(&child == GetChild(Direction::LEFT) || &child == GetChild(Direction::RIGHT)); return &child == GetChild(Direction::LEFT) ? Direction::LEFT : Direction::RIGHT; } constexpr void ReplaceChild(RedBlackTreeNode &old_child, RedBlackTreeNode *new_child) noexcept { SetChild(GetChildDirection(old_child), new_child); } constexpr void ReplaceChild(RedBlackTreeNode &old_child, RedBlackTreeNode &new_child) noexcept { SetChild(GetChildDirection(old_child), new_child); } [[nodiscard]] constexpr Direction GetDirectionInParent() const noexcept { assert(parent != nullptr); assert(!IsHead()); return parent->GetChildDirection(*this); } [[nodiscard]] auto &Rotate(RedBlackTreeNode::Direction direction) noexcept { assert(!IsHead()); auto *x = GetOtherChild(direction); assert(x != nullptr); auto *y = x->SetChild(direction, this); SetOtherChild(direction, y); return *x; } void RotateInParent(RedBlackTreeNode::Direction direction) noexcept { assert(parent != nullptr); assert(!IsHead()); auto &p = *parent; const auto direction_in_parent = p.GetChildDirection(*this); auto &new_node = Rotate(direction); assert(new_node.parent == this); assert(p.GetChild(direction_in_parent) == this); p.SetChild(direction_in_parent, new_node); } [[nodiscard]] constexpr static RedBlackTreeNode &GetLeftMost(RedBlackTreeNode *node) noexcept { assert(node != nullptr); assert(!node->IsHead()); while (auto *left = node->GetChild(Direction::LEFT)) { assert(left->parent == node); node = left; } return *node; } [[nodiscard]] constexpr RedBlackTreeNode &GetLeftMost() noexcept { return GetLeftMost(this); } #ifndef NDEBUG /** * Determine the "black height" (the number of black nodes in * any path from the root to the leaves). This is for * debugging only. It walks the whole tree and aborts if the * black height is not consistent. */ static unsigned BlackHeight(const RedBlackTreeNode *node) noexcept { if (node == nullptr) /* leaf nodes (NIL / nullptr) count as black */ return 1; assert(node->parent != nullptr); assert(node->color != Color::HEAD); assert(node->color != Color::RED || node->parent->color != Color::RED); assert(node->children[0] == nullptr || node->children[0]->parent == node); assert(node->children[1] == nullptr || node->children[1]->parent == node); const unsigned left_height = BlackHeight(node->children[0]); const unsigned right_height = BlackHeight(node->children[1]); /* the black height must be equal in all paths */ assert(left_height == right_height); return left_height + (node->color == Color::BLACK); } #endif private: [[nodiscard]] constexpr static RedBlackTreeNode *GetLeftHandedParent(RedBlackTreeNode *node) noexcept { assert(node != nullptr); assert(!node->IsHead()); while (true) { assert(node->parent != nullptr); auto &p = *node->parent; if (p.IsHead()) return nullptr; assert(node->color != RedBlackTreeNode::Color::RED || p.color != RedBlackTreeNode::Color::RED); if (p.GetChildDirection(*node) == Direction::LEFT) return &p; node = &p; } } public: [[nodiscard]] constexpr static RedBlackTreeNode *GetNextNode(RedBlackTreeNode *node) noexcept { assert(node != nullptr); assert(!node->IsHead()); if (auto *right = node->GetChild(Direction::RIGHT)) { assert(node->color != RedBlackTreeNode::Color::RED || right->color != RedBlackTreeNode::Color::RED); return &right->GetLeftMost(); } assert(node->parent != nullptr); auto &p = *node->parent; if (p.IsHead()) return nullptr; if (p.GetChildDirection(*node) == Direction::LEFT) return &p; return GetLeftHandedParent(&p); } private: [[nodiscard]] constexpr bool HasTwoChildren() const noexcept { return children[0] != nullptr && children[1] != nullptr; } constexpr RedBlackTreeNode *GetAnyChild() const noexcept { return children[children[1] != nullptr]; } public: constexpr void Unlink() noexcept { assert(parent != nullptr); assert(!IsHead()); if (HasTwoChildren()) { /* swap with successor, because it, by definition, doesn't have two children; the rest of this method assumes we have exactly one child or none */ auto &right = *GetRight(); auto &successor = right.GetLeftMost(); auto &p = *parent; const auto direction_in_parent = p.GetChildDirection(*this); std::swap(color, successor.color); successor.SetChild(Direction::LEFT, GetLeft()); SetChild(Direction::LEFT, nullptr); SetChild(Direction::RIGHT, successor.GetRight()); if (&successor == &right) { assert(successor.parent == this); successor.SetChild(Direction::RIGHT, *this); } else { assert(successor.parent != this); successor.parent->SetChild(Direction::LEFT, *this); successor.SetChild(Direction::RIGHT, &right); } p.SetChild(direction_in_parent, successor); } else { /* if there is exactly one child, it must be red */ assert(GetAnyChild() == nullptr || GetAnyChild()->color == Color::RED); } assert(!HasTwoChildren()); auto &p = *parent; if (auto *child = GetAnyChild()) { p.ReplaceChild(*this, *child); child->color = Color::BLACK; } else if (IsRoot()) { p.SetChild(Direction::LEFT, nullptr); } else { if (color == Color::BLACK) FixDoubleBlack(); p.ReplaceChild(*this, nullptr); } } private: constexpr std::pair GetRedChild() const noexcept { if (auto *left = GetLeft(); left != nullptr && left->color == Color::RED) return {Direction::LEFT, left}; if (auto *right = GetRight(); right != nullptr && right->color == Color::RED) return {Direction::RIGHT, right}; return {}; } constexpr void FixDoubleBlack() noexcept { assert(parent != nullptr); assert(!IsHead()); assert(color == Color::BLACK); if (IsRoot()) return; auto &p = *parent; const auto direction = p.GetChildDirection(*this); const auto other_direction = OtherDirection(direction); auto *const sibling = p.GetChild(other_direction); if (sibling == nullptr) { p.FixDoubleBlack(); return; } switch (sibling->color) { case Color::RED: p.color = Color::RED; sibling->color = Color::BLACK; p.RotateInParent(direction); FixDoubleBlack(); break; case Color::BLACK: if (const auto [red_direction, red] = sibling->GetRedChild(); red != nullptr) { /* at least one red child */ if (direction == red_direction) { red->color = p.color; sibling->RotateInParent(other_direction); } else { red->color = sibling->color; sibling->color = p.color; } p.RotateInParent(direction); p.color = Color::BLACK; } else { /* no red child (both children are either black or nullptr) */ sibling->color = Color::RED; if (p.color == Color::BLACK) p.FixDoubleBlack(); else p.color = Color::BLACK; } break; case Color::HEAD: // unreachable assert(false); break; } } };