// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // This module is browser compatible. import { ascend } from "./comparators.ts"; import { BinarySearchTree } from "./binary_search_tree.ts"; import { type Direction, RedBlackNode } from "./_red_black_node.ts"; import { internals } from "./_binary_search_tree_internals.ts"; const { getRoot, setRoot, getCompare, findNode, rotateNode, insertNode, removeNode, } = internals; /** * A red-black tree. This is a kind of self-balancing binary search tree. The * values are in ascending order by default, using JavaScript's built-in * comparison operators to sort the values. * * Red-Black Trees require fewer rotations than AVL Trees, so they can provide * faster insertions and removal operations. If you need faster lookups, you * should use an AVL Tree instead. AVL Trees are more strictly balanced than * Red-Black Trees, so they can provide faster lookups. * * | Method | Average Case | Worst Case | * | ------------- | ------------ | ---------- | * | find(value) | O(log n) | O(log n) | * | insert(value) | O(log n) | O(log n) | * | remove(value) | O(log n) | O(log n) | * | min() | O(log n) | O(log n) | * | max() | O(log n) | O(log n) | * * @example Usage * ```ts * import { * ascend, * descend, * RedBlackTree, * } from "@std/data-structures"; * import { assertEquals } from "@std/assert"; * * const values = [3, 10, 13, 4, 6, 7, 1, 14]; * const tree = new RedBlackTree(); * values.forEach((value) => tree.insert(value)); * assertEquals([...tree], [1, 3, 4, 6, 7, 10, 13, 14]); * assertEquals(tree.min(), 1); * assertEquals(tree.max(), 14); * assertEquals(tree.find(42), null); * assertEquals(tree.find(7), 7); * assertEquals(tree.remove(42), false); * assertEquals(tree.remove(7), true); * assertEquals([...tree], [1, 3, 4, 6, 10, 13, 14]); * * const invertedTree = new RedBlackTree(descend); * values.forEach((value) => invertedTree.insert(value)); * assertEquals([...invertedTree], [14, 13, 10, 7, 6, 4, 3, 1]); * assertEquals(invertedTree.min(), 14); * assertEquals(invertedTree.max(), 1); * assertEquals(invertedTree.find(42), null); * assertEquals(invertedTree.find(7), 7); * assertEquals(invertedTree.remove(42), false); * assertEquals(invertedTree.remove(7), true); * assertEquals([...invertedTree], [14, 13, 10, 6, 4, 3, 1]); * * const words = new RedBlackTree((a, b) => * ascend(a.length, b.length) || ascend(a, b) * ); * ["truck", "car", "helicopter", "tank", "train", "suv", "semi", "van"] * .forEach((value) => words.insert(value)); * assertEquals([...words], [ * "car", * "suv", * "van", * "semi", * "tank", * "train", * "truck", * "helicopter", * ]); * assertEquals(words.min(), "car"); * assertEquals(words.max(), "helicopter"); * assertEquals(words.find("scooter"), null); * assertEquals(words.find("tank"), "tank"); * assertEquals(words.remove("scooter"), false); * assertEquals(words.remove("tank"), true); * assertEquals([...words], [ * "car", * "suv", * "van", * "semi", * "train", * "truck", * "helicopter", * ]); * ``` * * @typeparam T The type of the values being stored in the tree. */ export class RedBlackTree extends BinarySearchTree { /** * Construct an empty red-black tree. * * @param compare A custom comparison function for the values. The default comparison function sorts by ascending order. */ constructor(compare: (a: T, b: T) => number = ascend) { if (typeof compare !== "function") { throw new TypeError( "Cannot construct a RedBlackTree: the 'compare' parameter is not a function, did you mean to call RedBlackTree.from?", ); } super(compare); } /** * Create a new red-black tree from an array like, an iterable object, or * an existing red-black tree. * * A custom comparison function can be provided to sort the values in a * specific order. By default, the values are sorted in ascending order, * unless a {@link RedBlackTree} is passed, in which case the comparison * function is copied from the input tree. * * @example Creating a red-black tree from an array like * ```ts no-assert * import { RedBlackTree } from "@std/data-structures"; * * const tree = RedBlackTree.from([3, 10, 13, 4, 6, 7, 1, 14]); * ``` * * @example Creating a red-black tree from an iterable object * ```ts no-assert * import { RedBlackTree } from "@std/data-structures"; * * const tree = RedBlackTree.from((function*() { * yield 3; * yield 10; * yield 13; * })()); * ``` * * @example Creating a red-black tree from an existing red-black tree * ```ts no-assert * import { RedBlackTree } from "@std/data-structures"; * * const tree = RedBlackTree.from([3, 10, 13, 4, 6, 7, 1, 14]); * const copy = RedBlackTree.from(tree); * ``` * * @example Creating a red-black tree from an array like with a custom comparison function * ```ts no-assert * import { RedBlackTree, descend } from "@std/data-structures"; * * const tree = RedBlackTree.from([3, 10, 13, 4, 6, 7, 1, 14], { * compare: descend, * }); * ``` * * @typeparam T The type of the values being stored in the tree. * @param collection An array like, an iterable, or existing red-black tree. * @param options An optional options object to customize the comparison function. * @returns A new red-black tree with the values from the passed collection. */ static override from( collection: ArrayLike | Iterable | RedBlackTree, options?: { compare?: (a: T, b: T) => number; }, ): RedBlackTree; /** * Create a new red-black tree from an array like, an iterable object, or * an existing red-black tree. * * A custom mapping function can be provided to transform the values before * inserting them into the tree. * * A custom comparison function can be provided to sort the values in a * specific order. A custom mapping function can be provided to transform the * values before inserting them into the tree. By default, the values are * sorted in ascending order, unless a {@link RedBlackTree} is passed, in * which case the comparison function is copied from the input tree. The * comparison operator is used to sort the values in the tree after mapping * the values. * * @example Creating a red-black tree from an array like with a custom mapping function * ```ts no-assert * import { RedBlackTree } from "@std/data-structures"; * * const tree = RedBlackTree.from([3, 10, 13, 4, 6, 7, 1, 14], { * map: (value) => value.toString(), * }); * ``` * @typeparam T The type of the values in the passed collection. * @typeparam U The type of the values being stored in the red-black tree. * @typeparam V The type of the `this` context in the mapping function. Defaults to `undefined`. * @param collection An array like, an iterable, or existing red-black tree. * @param options The options object to customize the mapping and comparison functions. The `thisArg` property can be used to set the `this` value when calling the mapping function. * @returns A new red-black tree with the mapped values from the passed collection. */ static override from( collection: ArrayLike | Iterable | RedBlackTree, options: { compare?: (a: U, b: U) => number; map: (value: T, index: number) => U; thisArg?: V; }, ): RedBlackTree; static override from( collection: ArrayLike | Iterable | RedBlackTree, options?: { compare?: (a: U, b: U) => number; map?: (value: T, index: number) => U; thisArg?: V; }, ): RedBlackTree { let result: RedBlackTree; let unmappedValues: ArrayLike | Iterable = []; if (collection instanceof RedBlackTree) { result = new RedBlackTree( options?.compare ?? getCompare(collection as unknown as RedBlackTree), ); if (options?.compare || options?.map) { unmappedValues = collection; } else { const nodes: RedBlackNode[] = []; const root = getRoot(collection); if (root) { setRoot(result, root as unknown as RedBlackNode); nodes.push(root as unknown as RedBlackNode); } while (nodes.length) { const node: RedBlackNode = nodes.pop()!; const left: RedBlackNode | null = node.left ? RedBlackNode.from(node.left) : null; const right: RedBlackNode | null = node.right ? RedBlackNode.from(node.right) : null; if (left) { left.parent = node; nodes.push(left); } if (right) { right.parent = node; nodes.push(right); } } } } else { result = (options?.compare ? new RedBlackTree(options.compare) : new RedBlackTree()) as RedBlackTree; unmappedValues = collection; } const values: Iterable = options?.map ? Array.from(unmappedValues, options.map, options.thisArg) : unmappedValues as U[]; for (const value of values) result.insert(value); return result; } #removeFixup( parent: RedBlackNode | null, current: RedBlackNode | null, ) { while (parent && !current?.red) { const direction: Direction = parent.left === current ? "left" : "right"; const siblingDirection: Direction = direction === "right" ? "left" : "right"; let sibling: RedBlackNode | null = parent[siblingDirection]; if (sibling?.red) { sibling.red = false; parent.red = true; rotateNode(this, parent, direction); sibling = parent[siblingDirection]; } if (sibling) { if (!sibling.left?.red && !sibling.right?.red) { sibling!.red = true; current = parent; parent = current.parent; } else { if (!sibling[siblingDirection]?.red) { sibling[direction]!.red = false; sibling.red = true; rotateNode(this, sibling, siblingDirection); sibling = parent[siblingDirection!]; } sibling!.red = parent.red; parent.red = false; sibling![siblingDirection]!.red = false; rotateNode(this, parent, direction); current = getRoot(this) as RedBlackNode; parent = null; } } } if (current) current.red = false; } /** * Add a value to the red-black tree if it does not already exist in the tree. * * The complexity of this operation is on average and at worst O(log n), where * n is the number of values in the tree. * * @example Inserting a value into the tree * ```ts * import { RedBlackTree } from "@std/data-structures"; * import { assertEquals } from "@std/assert"; * * const tree = new RedBlackTree(); * * assertEquals(tree.insert(42), true); * assertEquals(tree.insert(42), false); * ``` * * @param value The value to insert into the tree. * @returns `true` if the value was inserted, `false` if the value already exists in the tree. */ override insert(value: T): boolean { let node = insertNode( this, RedBlackNode, value, ) as (RedBlackNode | null); if (node) { while (node.parent?.red) { let parent: RedBlackNode = node.parent!; const parentDirection: Direction = parent.directionFromParent()!; const uncleDirection: Direction = parentDirection === "right" ? "left" : "right"; const uncle: RedBlackNode | null = parent.parent![uncleDirection] ?? null; if (uncle?.red) { parent.red = false; uncle.red = false; parent.parent!.red = true; node = parent.parent!; } else { if (node === parent[uncleDirection]) { node = parent; rotateNode(this, node, parentDirection); parent = node.parent!; } parent.red = false; parent.parent!.red = true; rotateNode(this, parent.parent!, uncleDirection); } } (getRoot(this) as RedBlackNode).red = false; } return !!node; } /** * Remove a value from the red-black tree if it exists in the tree. * * The complexity of this operation is on average and at worst O(log n), where * n is the number of values in the tree. * * @example Removing values from the tree * ```ts * import { RedBlackTree } from "@std/data-structures"; * import { assertEquals } from "@std/assert"; * * const tree = RedBlackTree.from([42]); * * assertEquals(tree.remove(42), true); * assertEquals(tree.remove(42), false); * ``` * * @param value The value to remove from the tree. * @returns `true` if the value was found and removed, `false` if the value was not found in the tree. */ override remove(value: T): boolean { const node = findNode(this, value) as (RedBlackNode | null); if (!node) { return false; } const removedNode = removeNode(this, node) as ( | RedBlackNode | null ); if (removedNode && !removedNode.red) { this.#removeFixup( removedNode.parent, removedNode.left ?? removedNode.right, ); } return true; } }