UNPKG

bst-typed

Version:
902 lines (901 loc) 29.8 kB
"use strict"; /** * data-structure-typed * * @author Pablo Zeng * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com> * @license MIT License */ var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FibonacciHeap = exports.FibonacciHeapNode = exports.Heap = void 0; const base_1 = require("../base"); /** * Binary heap with pluggable comparator; supports fast insertion and removal of the top element. * @remarks Time O(1), Space O(1) * @template E * @template R * 1. Complete Binary Tree: Heaps are typically complete binary trees, meaning every level is fully filled except possibly for the last level, which has nodes as far left as possible. * 2. Heap Properties: Each node in a heap follows a specific order property, which varies depending on the type of heap: * Max Heap: The value of each parent node is greater than or equal to the value of its children. * Min Heap: The value of each parent node is less than or equal to the value of its children. * 3. Root Node Access: In a heap, the largest element (in a max heap) or the smallest element (in a min heap) is always at the root of the tree. * 4. Efficient Insertion and Deletion: Due to its structure, a heap allows for insertion and deletion operations in logarithmic time (O(log n)). * 5. Managing Dynamic Data Sets: Heaps effectively manage dynamic data sets, especially when frequent access to the largest or smallest elements is required. * 6. Non-linear Search: While a heap allows rapid access to its largest or smallest element, it is less efficient for other operations, such as searching for a specific element, as it is not designed for these tasks. * 7. Efficient Sorting Algorithms: For example, heap sort. Heap sort uses the properties of a heap to sort elements. * 8. Graph Algorithms: Such as Dijkstra's shortest path algorithm and Prime's minimum-spanning tree algorithm, which use heaps to improve performance. * @example * // Use Heap to sort an array * function heapSort(arr: number[]): number[] { * const heap = new Heap<number>(arr, { comparator: (a, b) => a - b }); * const sorted: number[] = []; * while (!heap.isEmpty()) { * sorted.push(heap.poll()!); // Poll minimum element * } * return sorted; * } * * const array = [5, 3, 8, 4, 1, 2]; * console.log(heapSort(array)); // [1, 2, 3, 4, 5, 8] * @example * // Use Heap to solve top k problems * function topKElements(arr: number[], k: number): number[] { * const heap = new Heap<number>([], { comparator: (a, b) => b - a }); // Max heap * arr.forEach(num => { * heap.add(num); * if (heap.size > k) heap.poll(); // Keep the heap size at K * }); * return heap.toArray(); * } * * const numbers = [10, 30, 20, 5, 15, 25]; * console.log(topKElements(numbers, 3)); // [15, 10, 5] * @example * // Use Heap to merge sorted sequences * function mergeSortedSequences(sequences: number[][]): number[] { * const heap = new Heap<{ value: number; seqIndex: number; itemIndex: number }>([], { * comparator: (a, b) => a.value - b.value // Min heap * }); * * // Initialize heap * sequences.forEach((seq, seqIndex) => { * if (seq.length) { * heap.add({ value: seq[0], seqIndex, itemIndex: 0 }); * } * }); * * const merged: number[] = []; * while (!heap.isEmpty()) { * const { value, seqIndex, itemIndex } = heap.poll()!; * merged.push(value); * * if (itemIndex + 1 < sequences[seqIndex].length) { * heap.add({ * value: sequences[seqIndex][itemIndex + 1], * seqIndex, * itemIndex: itemIndex + 1 * }); * } * } * * return merged; * } * * const sequences = [ * [1, 4, 7], * [2, 5, 8], * [3, 6, 9] * ]; * console.log(mergeSortedSequences(sequences)); // [1, 2, 3, 4, 5, 6, 7, 8, 9] * @example * // Use Heap to dynamically maintain the median * class MedianFinder { * private low: MaxHeap<number>; // Max heap, stores the smaller half * private high: MinHeap<number>; // Min heap, stores the larger half * * constructor() { * this.low = new MaxHeap<number>([]); * this.high = new MinHeap<number>([]); * } * * addNum(num: number): void { * if (this.low.isEmpty() || num <= this.low.peek()!) this.low.add(num); * else this.high.add(num); * * // Balance heaps * if (this.low.size > this.high.size + 1) this.high.add(this.low.poll()!); * else if (this.high.size > this.low.size) this.low.add(this.high.poll()!); * } * * findMedian(): number { * if (this.low.size === this.high.size) return (this.low.peek()! + this.high.peek()!) / 2; * return this.low.peek()!; * } * } * * const medianFinder = new MedianFinder(); * medianFinder.addNum(10); * console.log(medianFinder.findMedian()); // 10 * medianFinder.addNum(20); * console.log(medianFinder.findMedian()); // 15 * medianFinder.addNum(30); * console.log(medianFinder.findMedian()); // 20 * medianFinder.addNum(40); * console.log(medianFinder.findMedian()); // 25 * medianFinder.addNum(50); * console.log(medianFinder.findMedian()); // 30 * @example * // Use Heap for load balancing * function loadBalance(requests: number[], servers: number): number[] { * const serverHeap = new Heap<{ id: number; load: number }>([], { comparator: (a, b) => a.load - b.load }); // min heap * const serverLoads = new Array(servers).fill(0); * * for (let i = 0; i < servers; i++) { * serverHeap.add({ id: i, load: 0 }); * } * * requests.forEach(req => { * const server = serverHeap.poll()!; * serverLoads[server.id] += req; * server.load += req; * serverHeap.add(server); // The server after updating the load is re-entered into the heap * }); * * return serverLoads; * } * * const requests = [5, 2, 8, 3, 7]; * console.log(loadBalance(requests, 3)); // [12, 8, 5] * @example * // Use Heap to schedule tasks * type Task = [string, number]; * * function scheduleTasks(tasks: Task[], machines: number): Map<number, Task[]> { * const machineHeap = new Heap<{ id: number; load: number }>([], { comparator: (a, b) => a.load - b.load }); // Min heap * const allocation = new Map<number, Task[]>(); * * // Initialize the load on each machine * for (let i = 0; i < machines; i++) { * machineHeap.add({ id: i, load: 0 }); * allocation.set(i, []); * } * * // Assign tasks * tasks.forEach(([task, load]) => { * const machine = machineHeap.poll()!; * allocation.get(machine.id)!.push([task, load]); * machine.load += load; * machineHeap.add(machine); // The machine after updating the load is re-entered into the heap * }); * * return allocation; * } * * const tasks: Task[] = [ * ['Task1', 3], * ['Task2', 1], * ['Task3', 2], * ['Task4', 5], * ['Task5', 4] * ]; * const expectedMap = new Map<number, Task[]>(); * expectedMap.set(0, [ * ['Task1', 3], * ['Task4', 5] * ]); * expectedMap.set(1, [ * ['Task2', 1], * ['Task3', 2], * ['Task5', 4] * ]); * console.log(scheduleTasks(tasks, 2)); // expectedMap */ class Heap extends base_1.IterableElementBase { /** * Create a Heap and optionally bulk-insert elements. * @remarks Time O(N), Space O(N) * @param [elements] - Iterable of elements (or raw values if toElementFn is set). * @param [options] - Options such as comparator and toElementFn. * @returns New Heap instance. */ constructor(elements = [], options) { super(options); this._equals = Object.is; this._elements = []; this._DEFAULT_COMPARATOR = (a, b) => { if (typeof a === 'object' || typeof b === 'object') { throw TypeError('When comparing object types, define a custom comparator in options.'); } if (a > b) return 1; if (a < b) return -1; return 0; }; this._comparator = this._DEFAULT_COMPARATOR; /** * Get the comparator used to order elements. * @remarks Time O(1), Space O(1) * @returns Comparator function. */ if (options) { const { comparator } = options; if (comparator) this._comparator = comparator; } this.addMany(elements); } /** * Get the backing array of the heap. * @remarks Time O(1), Space O(1) * @returns Internal elements array. */ get elements() { return this._elements; } /** * Get the number of elements. * @remarks Time O(1), Space O(1) * @returns Heap size. */ get size() { return this.elements.length; } /** * Get the last leaf element. * @remarks Time O(1), Space O(1) * @returns Last element or undefined. */ get leaf() { var _a; return (_a = this.elements[this.size - 1]) !== null && _a !== void 0 ? _a : undefined; } /** * Create a heap of the same class from an iterable. * @remarks Time O(N), Space O(N) * @template T * @template R * @template S * @param [elements] - Iterable of elements or raw records. * @param [options] - Heap options including comparator. * @returns A new heap instance of this class. */ static from(elements, options) { return new this(elements, options); } /** * Build a Heap from an iterable in linear time given a comparator. * @remarks Time O(N), Space O(N) * @template EE * @template RR * @param elements - Iterable of elements. * @param options - Heap options including comparator. * @returns A new Heap built from elements. */ static heapify(elements, options) { return new Heap(elements, options); } /** * Insert an element. * @remarks Time O(1) amortized, Space O(1) * @param element - Element to insert. * @returns True. */ add(element) { this._elements.push(element); return this._bubbleUp(this.elements.length - 1); } /** * Insert many elements from an iterable. * @remarks Time O(N log N), Space O(1) * @param elements - Iterable of elements or raw values. * @returns Array of per-element success flags. */ addMany(elements) { const flags = []; for (const el of elements) { if (this.toElementFn) { const ok = this.add(this.toElementFn(el)); flags.push(ok); } else { const ok = this.add(el); flags.push(ok); } } return flags; } /** * Remove and return the top element. * @remarks Time O(log N), Space O(1) * @returns Top element or undefined. */ poll() { if (this.elements.length === 0) return; const value = this.elements[0]; const last = this.elements.pop(); if (this.elements.length) { this.elements[0] = last; this._sinkDown(0, this.elements.length >> 1); } return value; } /** * Get the current top element without removing it. * @remarks Time O(1), Space O(1) * @returns Top element or undefined. */ peek() { return this.elements[0]; } /** * Check whether the heap is empty. * @remarks Time O(1), Space O(1) * @returns True if size is 0. */ isEmpty() { return this.size === 0; } /** * Remove all elements. * @remarks Time O(1), Space O(1) * @returns void */ clear() { this._elements = []; } /** * Replace the backing array and rebuild the heap. * @remarks Time O(N), Space O(N) * @param elements - Iterable used to refill the heap. * @returns Array of per-node results from fixing steps. */ refill(elements) { this._elements = Array.from(elements); return this.fix(); } /** * Check if an equal element exists in the heap. * @remarks Time O(N), Space O(1) * @param element - Element to search for. * @returns True if found. */ has(element) { for (const el of this.elements) if (this._equals(el, element)) return true; return false; } /** * Delete one occurrence of an element. * @remarks Time O(N), Space O(1) * @param element - Element to delete. * @returns True if an element was removed. */ delete(element) { let index = -1; for (let i = 0; i < this.elements.length; i++) { if (this._equals(this.elements[i], element)) { index = i; break; } } if (index < 0) return false; if (index === 0) { this.poll(); } else if (index === this.elements.length - 1) { this.elements.pop(); } else { this.elements.splice(index, 1, this.elements.pop()); this._bubbleUp(index); this._sinkDown(index, this.elements.length >> 1); } return true; } /** * Delete the first element that matches a predicate. * @remarks Time O(N), Space O(1) * @param predicate - Function (element, index, heap) → boolean. * @returns True if an element was removed. */ deleteBy(predicate) { let idx = -1; for (let i = 0; i < this.elements.length; i++) { if (predicate(this.elements[i], i, this)) { idx = i; break; } } if (idx < 0) return false; if (idx === 0) { this.poll(); } else if (idx === this.elements.length - 1) { this.elements.pop(); } else { this.elements.splice(idx, 1, this.elements.pop()); this._bubbleUp(idx); this._sinkDown(idx, this.elements.length >> 1); } return true; } /** * Set the equality comparator used by has/delete operations. * @remarks Time O(1), Space O(1) * @param equals - Equality predicate (a, b) → boolean. * @returns This heap. */ setEquality(equals) { this._equals = equals; return this; } /** * Traverse the binary heap as a complete binary tree and collect elements. * @remarks Time O(N), Space O(H) * @param [order] - Traversal order: 'PRE' | 'IN' | 'POST'. * @returns Array of visited elements. */ dfs(order = 'PRE') { const result = []; const _dfs = (index) => { const left = 2 * index + 1, right = left + 1; if (index < this.size) { if (order === 'IN') { _dfs(left); result.push(this.elements[index]); _dfs(right); } else if (order === 'PRE') { result.push(this.elements[index]); _dfs(left); _dfs(right); } else if (order === 'POST') { _dfs(left); _dfs(right); result.push(this.elements[index]); } } }; _dfs(0); return result; } /** * Restore heap order bottom-up (heapify in-place). * @remarks Time O(N), Space O(1) * @returns Array of per-node results from fixing steps. */ fix() { const results = []; for (let i = Math.floor(this.size / 2) - 1; i >= 0; i--) { results.push(this._sinkDown(i, this.elements.length >> 1)); } return results; } /** * Return all elements in ascending order by repeatedly polling. * @remarks Time O(N log N), Space O(N) * @returns Sorted array of elements. */ sort() { const visited = []; const cloned = this._createInstance(); for (const x of this.elements) cloned.add(x); while (!cloned.isEmpty()) { const top = cloned.poll(); if (top !== undefined) visited.push(top); } return visited; } /** * Deep clone this heap. * @remarks Time O(N), Space O(N) * @returns A new heap with the same elements. */ clone() { const next = this._createInstance(); for (const x of this.elements) next.add(x); return next; } /** * Filter elements into a new heap of the same class. * @remarks Time O(N log N), Space O(N) * @param callback - Predicate (element, index, heap) → boolean to keep element. * @param [thisArg] - Value for `this` inside the callback. * @returns A new heap with the kept elements. */ filter(callback, thisArg) { const out = this._createInstance(); let i = 0; for (const x of this) { if (thisArg === undefined ? callback(x, i++, this) : callback.call(thisArg, x, i++, this)) { out.add(x); } else { i++; } } return out; } /** * Map elements into a new heap of possibly different element type. * @remarks Time O(N log N), Space O(N) * @template EM * @template RM * @param callback - Mapping function (element, index, heap) → newElement. * @param options - Options for the output heap, including comparator for EM. * @param [thisArg] - Value for `this` inside the callback. * @returns A new heap with mapped elements. */ map(callback, options, thisArg) { const _a = options !== null && options !== void 0 ? options : {}, { comparator, toElementFn } = _a, rest = __rest(_a, ["comparator", "toElementFn"]); if (!comparator) throw new TypeError('Heap.map requires options.comparator for EM'); const out = this._createLike([], Object.assign(Object.assign({}, rest), { comparator, toElementFn })); let i = 0; for (const x of this) { const v = thisArg === undefined ? callback(x, i++, this) : callback.call(thisArg, x, i++, this); out.add(v); } return out; } /** * Map elements into a new heap of the same element type. * @remarks Time O(N log N), Space O(N) * @param callback - Mapping function (element, index, heap) → element. * @param [thisArg] - Value for `this` inside the callback. * @returns A new heap with mapped elements. */ mapSame(callback, thisArg) { const out = this._createInstance(); let i = 0; for (const x of this) { const v = thisArg === undefined ? callback(x, i++, this) : callback.call(thisArg, x, i++, this); out.add(v); } return out; } /** * Get the comparator used to order elements. * @remarks Time O(1), Space O(1) * @returns Comparator function. */ get comparator() { return this._comparator; } *_getIterator() { for (const element of this.elements) yield element; } _bubbleUp(index) { const element = this.elements[index]; while (index > 0) { const parent = (index - 1) >> 1; const parentItem = this.elements[parent]; if (this.comparator(parentItem, element) <= 0) break; this.elements[index] = parentItem; index = parent; } this.elements[index] = element; return true; } _sinkDown(index, halfLength) { const element = this.elements[index]; while (index < halfLength) { let left = (index << 1) | 1; const right = left + 1; let minItem = this.elements[left]; if (right < this.elements.length && this.comparator(minItem, this.elements[right]) > 0) { left = right; minItem = this.elements[right]; } if (this.comparator(minItem, element) >= 0) break; this.elements[index] = minItem; index = left; } this.elements[index] = element; return true; } /** * (Protected) Create an empty instance of the same concrete class. * @remarks Time O(1), Space O(1) * @param [options] - Options to override comparator or toElementFn. * @returns A like-kind empty heap instance. */ _createInstance(options) { const Ctor = this.constructor; const next = new Ctor([], Object.assign({ comparator: this.comparator, toElementFn: this.toElementFn }, (options !== null && options !== void 0 ? options : {}))); return next; } /** * (Protected) Create a like-kind instance seeded by elements. * @remarks Time O(N log N), Space O(N) * @template EM * @template RM * @param [elements] - Iterable of elements or raw values to seed. * @param [options] - Options forwarded to the constructor. * @returns A like-kind heap instance. */ _createLike(elements = [], options) { const Ctor = this.constructor; return new Ctor(elements, options); } /** * (Protected) Spawn an empty like-kind heap instance. * @remarks Time O(1), Space O(1) * @template EM * @template RM * @param [options] - Options forwarded to the constructor. * @returns An empty like-kind heap instance. */ _spawnLike(options) { return this._createLike([], options); } } exports.Heap = Heap; /** * Node container used by FibonacciHeap. * @remarks Time O(1), Space O(1) * @template E */ class FibonacciHeapNode { constructor(element, degree = 0) { this.element = element; this.degree = degree; this.marked = false; } } exports.FibonacciHeapNode = FibonacciHeapNode; /** * Fibonacci heap (min-heap) optimized for fast merges and amortized operations. * @remarks Time O(1), Space O(1) * @template E * @example examples will be generated by unit test */ class FibonacciHeap { /** * Create a FibonacciHeap. * @remarks Time O(1), Space O(1) * @param [comparator] - Comparator to order elements (min-heap by default). * @returns New FibonacciHeap instance. */ constructor(comparator) { this._size = 0; this.clear(); this._comparator = comparator || this._defaultComparator; if (typeof this.comparator !== 'function') throw new Error('FibonacciHeap: comparator must be a function.'); } /** * Get the circular root list head. * @remarks Time O(1), Space O(1) * @returns Root node or undefined. */ get root() { return this._root; } get size() { return this._size; } /** * Get the current minimum node. * @remarks Time O(1), Space O(1) * @returns Min node or undefined. */ get min() { return this._min; } get comparator() { return this._comparator; } clear() { this._root = undefined; this._min = undefined; this._size = 0; } add(element) { this.push(element); return true; } /** * Push an element into the root list. * @remarks Time O(1) amortized, Space O(1) * @param element - Element to insert. * @returns This heap. */ push(element) { const node = this._createNode(element); node.left = node; node.right = node; this.mergeWithRoot(node); if (!this.min || this.comparator(node.element, this.min.element) <= 0) this._min = node; this._size++; return this; } peek() { return this.min ? this.min.element : undefined; } /** * Collect nodes from a circular doubly linked list starting at head. * @remarks Time O(K), Space O(K) * @param [head] - Start node of the circular list. * @returns Array of nodes from the list. */ consumeLinkedList(head) { const elements = []; if (!head) return elements; let node = head; let started = false; while (true) { if (node === head && started) break; else if (node === head) started = true; elements.push(node); node = node.right; } return elements; } /** * Insert a node into a parent's child list (circular). * @remarks Time O(1), Space O(1) * @param parent - Parent node. * @param node - Child node to insert. * @returns void */ mergeWithChild(parent, node) { if (!parent.child) parent.child = node; else { node.right = parent.child.right; node.left = parent.child; parent.child.right.left = node; parent.child.right = node; } } poll() { return this.pop(); } /** * Remove and return the minimum element, consolidating the root list. * @remarks Time O(log N) amortized, Space O(1) * @returns Minimum element or undefined. */ pop() { if (this._size === 0) return undefined; const z = this.min; if (z.child) { const elements = this.consumeLinkedList(z.child); for (const node of elements) { this.mergeWithRoot(node); node.parent = undefined; } } this.removeFromRoot(z); if (z === z.right) { this._min = undefined; this._root = undefined; } else { this._min = z.right; this._consolidate(); } this._size--; return z.element; } /** * Meld another heap into this heap. * @remarks Time O(1), Space O(1) * @param heapToMerge - Another FibonacciHeap to meld into this one. * @returns void */ merge(heapToMerge) { if (heapToMerge.size === 0) return; if (this.root && heapToMerge.root) { const thisRoot = this.root, otherRoot = heapToMerge.root; const thisRootRight = thisRoot.right, otherRootLeft = otherRoot.left; thisRoot.right = otherRoot; otherRoot.left = thisRoot; thisRootRight.left = otherRootLeft; otherRootLeft.right = thisRootRight; } else if (!this.root && heapToMerge.root) { this._root = heapToMerge.root; } if (!this.min || (heapToMerge.min && this.comparator(heapToMerge.min.element, this.min.element) < 0)) { this._min = heapToMerge.min; } this._size += heapToMerge.size; heapToMerge.clear(); } _createNode(element) { return new FibonacciHeapNode(element); } isEmpty() { return this._size === 0; } _defaultComparator(a, b) { if (a < b) return -1; if (a > b) return 1; return 0; } mergeWithRoot(node) { if (!this.root) this._root = node; else { node.right = this.root.right; node.left = this.root; this.root.right.left = node; this.root.right = node; } } removeFromRoot(node) { if (this.root === node) this._root = node.right; if (node.left) node.left.right = node.right; if (node.right) node.right.left = node.left; } _link(y, x) { this.removeFromRoot(y); y.left = y; y.right = y; this.mergeWithChild(x, y); x.degree++; y.parent = x; } _consolidate() { const A = new Array(this._size); const elements = this.consumeLinkedList(this.root); let x, y, d, t; for (const node of elements) { x = node; d = x.degree; while (A[d]) { y = A[d]; if (this.comparator(x.element, y.element) > 0) { t = x; x = y; y = t; } this._link(y, x); A[d] = undefined; d++; } A[d] = x; } for (let i = 0; i < A.length; i++) { if (A[i] && (!this.min || this.comparator(A[i].element, this.min.element) <= 0)) this._min = A[i]; } } } exports.FibonacciHeap = FibonacciHeap;