Home Manual Reference Source

src/types/RedBlackTree.js

import assert from 'assert';
import Node from './Node.js';
import BLACK from '../color/BLACK.js';
import RED from '../color/RED.js';
import predecessor from '../family/predecessor.js';
import insert from '../insertion/insert.js';
import insert_case2 from '../insertion/insert_case2.js';
import delete_one_child from '../deletion/delete_one_child.js';
import delete_no_child from '../deletion/delete_no_child.js';
import search from '../search/search.js';
import inordertraversal from '../traversal/inordertraversal.js';
import rangetraversal from '../traversal/rangetraversal.js';
import replace_node from '../deletion/replace_node.js';
import swap_non_adjacent from '../swap/swap_non_adjacent.js';
import swap_left from '../swap/swap_left.js';

/**
 * A RedBlackTree with key-only nodes.
 *
 */
export default class RedBlackTree {
	/**
	 * Constructs a new empty red-black tree.
	 *
	 * @param {Function} compare - The comparison function for node keys.
	 */
	constructor(compare) {
		assert(compare instanceof Function);
		/** @member {Function} The comparison function for node keys. */
		this.compare = compare;
		/** @member {Node} The root of the tree. */
		this.root = null;
	}

	/**
	 * Tells whether the tree is empty.
	 *
	 * @returns {boolean} true if empty, false otherwise.
	 */
	isEmpty() {
		return this.root === null;
	}

	/**
	 * Adds a key to the tree.
	 *
	 * @param {any} key - The key to add.
	 * @return {Node} The newly added node.
	 */
	add(key) {
		if (this.root === null) {
			this.root = new Node(BLACK, key);
			return this.root;
		}

		const node = new Node(RED, key);
		insert(this.compare, this.root, node);
		assert(node.parent !== null);
		if (node.parent._color !== BLACK) {
			const subtree = insert_case2(node);
			if (subtree.parent === null) {
				this.root = subtree;
			}
		}

		return node;
	}

	/**
	 * Search for the input key in the tree.
	 * Returns the first node whose key equals the input key.
	 * If no such node exists, returns <code>null</code>.
	 *
	 * @param {any} key - The input key.
	 * @returns {Node}
	 */
	_search(key) {
		if (this.root === null) return null;
		return search(this.compare, this.root, key);
	}

	/**
	 * Search for the input key in the tree. Returns the first node key found
	 * in this way (with {@link RedBlackTree#_search}. If no such key exists
	 * in the tree, returns <code>null</code>.
	 *
	 * @param {any} key - The input key.
	 * @returns {any}
	 */
	get(key) {
		const node = this._search(key);
		return node === null ? undefined : node.key;
	}

	/**
	 * Returns <code>true</code> if and only if the tree contains the input
	 * key.
	 *
	 * @param {any} key - The input key.
	 * @returns {boolean}
	 */
	has(key) {
		return this._search(key) !== null;
	}

	/**
	 * Deletes the input node from the tree.
	 *
	 * @param {Node} node - The input node to delete.
	 */
	unlink(node) {
		assert(node instanceof Node);
		if (node.left !== null) {
			// Swap node with its predecessor
			const pred = predecessor(node);
			// Delete predecessor node
			// NOTE: this node can have at most one non-leaf (left) child
			// because of red-black tree invariant.
			assert(pred.right === null);
			if (pred === node.left) {
				swap_left(node);
			} else {
				swap_non_adjacent(node, pred);
			}

			assert(node.right === null);
			if (node.left === null) {
				const subtree = delete_no_child(node);
				if (subtree.parent === null) {
					this.root = subtree;
				} else if (pred.parent === null) {
					assert(node === this.root);
					this.root = pred;
				}
			} else {
				delete_one_child(node);
				if (pred.parent === null) {
					assert(node === this.root);
					this.root = pred;
				}
			}
		} else if (node.right !== null) {
			/**
			 * Swap node with its successor.
			 *
			 * NOTE: Since pred is a leaf, there can only by one node in the
			 * right subtree, succ, which is necessarily red, hence
			 * node is black.
			 *
			 * The configuration:
			 *
			 *      (A)                 (B)                  (C)
			 *
			 *    p                   p                    p
			 *    |                   |                    |
			 *   node (BLACK)        succ (BLACK)        succ (BLACK)
			 *   / \                 / \                  / \
			 *  -  succ (RED)  ->   -  node (RED)  ->    -   -
			 *     / \                 / \
			 *    -   -               -   -
			 *
			 * NOTE: We take a shortcut and go directly from (A) to (C)
			 */
			assert(node.left === null);
			const succ = node.right;
			assert(succ._color === RED);
			succ._color = BLACK;
			if (node === this.root) {
				assert(node.parent === null);
				succ.parent = null;
				this.root = succ;
			} else {
				replace_node(node, succ);
			}
		} else if (node === this.root) {
			assert(node.parent === null);
			assert(node._color === BLACK);
			assert(node.left === null);
			assert(node.right === null);
			this.root = null;
		} else {
			assert(node.left === null);
			assert(node.right === null);
			const subtree = delete_no_child(node);
			if (subtree.parent === null) {
				this.root = subtree;
			}
		}
	}

	/**
	 * Search for the first node of the tree whose key equals the input key
	 * (with {@link RedBlackTree#_search}), then delete that node
	 * (with {@link RedBlackTree#unlink}). If such a node is found and deleted
	 * then return <code>true</code>. Return <code>false</code> otherwise.
	 *
	 * @param {any} key - The input key.
	 * @returns {boolean} - Whether the key existed in the tree before removal.
	 */
	remove(key) {
		const node = this._search(key);
		if (node === null) return false;

		this.unlink(node);
		return true;
	}

	/**
	 * Returns an in order iterator over the keys of the tree that lie in the
	 * interval [left, right[.
	 * @param {any} left - The left bound of the interval.
	 * @param {any} right - The right bound of the interval.
	 * @returns {IterableIterator}
	 */
	*range(left, right) {
		if (this.root !== null)
			yield* rangetraversal(this.compare, this.root, left, right);
	}

	/**
	 * Returns an in order iterator over the keys of the tree.
	 *
	 * @returns {IterableIterator}
	 */
	*items() {
		if (this.root !== null) yield* inordertraversal(this.root);
	}

	/**
	 * Same as {@link RedBlackTree#items}.
	 */
	[Symbol.iterator]() {
		return this.items();
	}

	/**
	 * Constructs an empty red-black tree.
	 *
	 * @param {Function} compare - The comparison function to use.
	 * @returns {RedBlackTree}
	 */
	static empty(compare) {
		return new RedBlackTree(compare);
	}

	/**
	 * Constructs a red-black tree from an input iterable.
	 *
	 * @param {Function} compare - The comparison function to use.
	 * @param {Iterable} iterable - The input iterable.
	 * @returns {RedBlackTree}
	 */
	static from(compare, iterable) {
		const tree = new RedBlackTree(compare);

		for (const element of iterable) tree.add(element);

		return tree;
	}
}