undirected-graph-typed
Version:
Undirected Graph
1,201 lines (648 loc) • 28.1 kB
TypeScript
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import type { Comparator, DFSOrderPattern, ElementCallback, HeapOptions } from '../../types';
import { IterableElementBase } from '../base';
/**
* Binary heap with pluggable comparator; supports fast insertion and removal of the top element.
* @remarks Typical operations: O(log N) insert/remove, O(1) peek. Space O(N).
* @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 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 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;
* @example
* // Get all elements as array
* const heap = new Heap<number>([5, 1, 3, 2, 4]);
* const arr = heap.toArray();
* console.log(arr.length); // 5;
* console.log(arr.sort()); // [1, 2, 3, 4, 5];
*/
export declare class Heap<E = any, R = any> extends IterableElementBase<E, R> {
protected _equals: (a: E, b: E) => boolean;
/**
* 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?: Iterable<E | R>, options?: HeapOptions<E, R>);
protected _elements: E[];
/**
* Get the backing array of the heap.
* @remarks Time O(1), Space O(1)
* @returns Internal elements array.
*/
get elements(): E[];
/**
* Get the number of elements.
* @remarks Time O(1), Space O(1)
* @returns Heap size.
* @example
* // Track heap capacity
* const heap = new Heap<number>();
* console.log(heap.size); // 0;
* heap.add(10);
* heap.add(20);
* console.log(heap.size); // 2;
* heap.poll();
* console.log(heap.size); // 1;
*/
get size(): number;
/**
* Get the last leaf element.
* @remarks Time O(1), Space O(1)
* @returns Last element or undefined.
*/
get leaf(): E | 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<T, R = any, S extends Heap<T, R> = Heap<T, R>>(this: new (elements?: Iterable<T | R>, options?: HeapOptions<T, R>) => S, elements?: Iterable<T | R>, options?: HeapOptions<T, R>): S;
/**
* 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<EE = any, RR = any>(elements: Iterable<EE>, options: HeapOptions<EE, RR>): Heap<EE, RR>;
/**
* Insert an element.
* @remarks Time O(log N) amortized, Space O(1)
* @param element - Element to insert.
* @returns True.
* @example
* // basic Heap creation and add operation
* // Create a min heap (default)
* const minHeap = new Heap([5, 3, 7, 1, 9, 2]);
*
* // Verify size
* console.log(minHeap.size); // 6;
*
* // Add new element
* minHeap.add(4);
* console.log(minHeap.size); // 7;
*
* // Min heap property: smallest element at root
* const min = minHeap.peek();
* console.log(min); // 1;
*/
add(element: E): boolean;
/**
* 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.
* @example
* // Add multiple elements
* const heap = new Heap<number>([], { comparator: (a, b) => a - b });
* heap.addMany([5, 3, 7, 1]);
* console.log(heap.peek()); // 1;
* console.log(heap.size); // 4;
*/
addMany(elements: Iterable<E | R>): boolean[];
/**
* Remove and return the top element.
* @remarks Time O(log N), Space O(1)
* @returns Top element or undefined.
* @example
* // Heap with custom comparator (MaxHeap behavior)
* interface Task {
* id: number;
* priority: number;
* name: string;
* }
*
* // Custom comparator for max heap behavior (higher priority first)
* const tasks: Task[] = [
* { id: 1, priority: 5, name: 'Email' },
* { id: 2, priority: 3, name: 'Chat' },
* { id: 3, priority: 8, name: 'Alert' }
* ];
*
* const maxHeap = new Heap(tasks, {
* comparator: (a: Task, b: Task) => b.priority - a.priority
* });
*
* console.log(maxHeap.size); // 3;
*
* // Peek returns highest priority task
* const topTask = maxHeap.peek();
* console.log(topTask?.priority); // 8;
* console.log(topTask?.name); // 'Alert';
*/
/**
* @deprecated Use `pop` instead. Will be removed in a future major version.
* @example
* // Heap with custom comparator (MaxHeap behavior)
* interface Task {
* id: number;
* priority: number;
* name: string;
* }
*
* // Custom comparator for max heap behavior (higher priority first)
* const tasks: Task[] = [
* { id: 1, priority: 5, name: 'Email' },
* { id: 2, priority: 3, name: 'Chat' },
* { id: 3, priority: 8, name: 'Alert' }
* ];
*
* const maxHeap = new Heap(tasks, {
* comparator: (a: Task, b: Task) => b.priority - a.priority
* });
*
* console.log(maxHeap.size); // 3;
*
* // Peek returns highest priority task
* const topTask = maxHeap.peek();
* console.log(topTask?.priority); // 8;
* console.log(topTask?.name); // 'Alert';
*/
poll(): E | undefined;
/**
* Remove and return the top element (min or max depending on comparator).
* @remarks Time O(log N) amortized, Space O(1)
* @returns The removed top element, or undefined if empty.
*/
pop(): E | undefined;
/**
* Get the current top element without removing it.
* @remarks Time O(1), Space O(1)
* @returns Top element or undefined.
* @example
* // Heap for event processing with priority
* interface Event {
* id: number;
* type: 'critical' | 'warning' | 'info';
* timestamp: number;
* message: string;
* }
*
* // Custom priority: critical > warning > info
* const priorityMap = { critical: 3, warning: 2, info: 1 };
*
* const eventHeap = new Heap<Event>([], {
* comparator: (a: Event, b: Event) => {
* const priorityA = priorityMap[a.type];
* const priorityB = priorityMap[b.type];
* return priorityB - priorityA; // Higher priority first
* }
* });
*
* // Add events in random order
* eventHeap.add({ id: 1, type: 'info', timestamp: 100, message: 'User logged in' });
* eventHeap.add({ id: 2, type: 'critical', timestamp: 101, message: 'Server down' });
* eventHeap.add({ id: 3, type: 'warning', timestamp: 102, message: 'High memory' });
* eventHeap.add({ id: 4, type: 'info', timestamp: 103, message: 'Cache cleared' });
* eventHeap.add({ id: 5, type: 'critical', timestamp: 104, message: 'Database error' });
*
* console.log(eventHeap.size); // 5;
*
* // Process events by priority (critical first)
* const processedOrder: Event[] = [];
* while (eventHeap.size > 0) {
* const event = eventHeap.poll();
* if (event) {
* processedOrder.push(event);
* }
* }
*
* // Verify critical events came first
* console.log(processedOrder[0].type); // 'critical';
* console.log(processedOrder[1].type); // 'critical';
* console.log(processedOrder[2].type); // 'warning';
* console.log(processedOrder[3].type); // 'info';
* console.log(processedOrder[4].type); // 'info';
*
* // Verify O(log n) operations
* const newHeap = new Heap<number>([5, 3, 7, 1]);
*
* // Add - O(log n)
* newHeap.add(2);
* console.log(newHeap.size); // 5;
*
* // Poll - O(log n)
* const removed = newHeap.poll();
* console.log(removed); // 1;
*
* // Peek - O(1)
* const top = newHeap.peek();
* console.log(top); // 2;
*/
peek(): E | undefined;
/**
* Check whether the heap is empty.
* @remarks Time O(1), Space O(1)
* @returns True if size is 0.
* @example
* // Check if heap is empty
* const heap = new Heap<number>([], { comparator: (a, b) => a - b });
* console.log(heap.isEmpty()); // true;
* heap.add(1);
* console.log(heap.isEmpty()); // false;
*/
isEmpty(): boolean;
/**
* Remove all elements.
* @remarks Time O(1), Space O(1)
* @returns void
* @example
* // Remove all elements
* const heap = new Heap<number>([1, 2, 3], { comparator: (a, b) => a - b });
* heap.clear();
* console.log(heap.isEmpty()); // true;
*/
clear(): void;
/**
* 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.
* @example
* // Check element existence
* const heap = new Heap<number>([3, 1, 2], { comparator: (a, b) => a - b });
* console.log(heap.has(1)); // true;
* console.log(heap.has(99)); // false;
*/
has(element: E): boolean;
/**
* 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.
* @example
* // Remove specific element
* const heap = new Heap<number>([3, 1, 4, 1, 5], { comparator: (a, b) => a - b });
* heap.delete(4);
* console.log(heap.toArray().includes(4)); // false;
*/
delete(element: E): boolean;
/**
* @deprecated Use `deleteWhere` instead. Will be removed in a future major version.
*/
deleteBy(predicate: (element: E, index: number, heap: this) => boolean): boolean;
/**
* 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.
*/
deleteWhere(predicate: (element: E, index: number, heap: this) => boolean): boolean;
/**
* 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: (a: E, b: E) => boolean): 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.
* @example
* // Depth-first traversal
* const heap = new Heap<number>([3, 1, 2], { comparator: (a, b) => a - b });
* const result = heap.dfs('IN');
* console.log(result.length); // 3;
*/
dfs(order?: DFSOrderPattern): E[];
/**
* 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(): boolean[];
/**
* Return all elements in ascending order by repeatedly polling.
* @remarks Time O(N log N), Space O(N)
* @returns Sorted array of elements.
* @example
* // Sort elements using heap
* const heap = new Heap<number>([5, 1, 3, 2, 4]);
* const sorted = heap.sort();
* console.log(sorted); // [1, 2, 3, 4, 5];
*/
sort(): E[];
/**
* Deep clone this heap.
* @remarks Time O(N), Space O(N)
* @returns A new heap with the same elements.
* @example
* // Create independent copy
* const heap = new Heap<number>([3, 1, 4], { comparator: (a, b) => a - b });
* const copy = heap.clone();
* copy.poll();
* console.log(heap.size); // 3;
* console.log(copy.size); // 2;
*/
clone(): this;
/**
* 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.
* @example
* // Filter elements
* const heap = new Heap<number>([1, 2, 3, 4, 5], { comparator: (a, b) => a - b });
* const evens = heap.filter(x => x % 2 === 0);
* console.log(evens.size); // 2;
*/
filter(callback: ElementCallback<E, R, boolean>, thisArg?: unknown): this;
/**
* 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.
* @example
* // Transform elements
* const heap = new Heap<number>([1, 2, 3], { comparator: (a, b) => a - b });
* const doubled = heap.map(x => x * 2, { comparator: (a, b) => a - b });
* console.log(doubled.peek()); // 2;
*/
map<EM, RM>(callback: ElementCallback<E, R, EM>, options: HeapOptions<EM, RM> & {
comparator: Comparator<EM>;
}, thisArg?: unknown): Heap<EM, RM>;
/**
* 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: ElementCallback<E, R, E>, thisArg?: unknown): this;
protected readonly _DEFAULT_COMPARATOR: Comparator<E>;
protected readonly _comparator: Comparator<E>;
/**
* Get the comparator used to order elements.
* @remarks Time O(1), Space O(1)
* @returns Comparator function.
*/
get comparator(): Comparator<E>;
protected _getIterator(): IterableIterator<E>;
protected _bubbleUp(index: number): boolean;
protected _sinkDown(index: number, halfLength: number): boolean;
/**
* (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.
*/
protected _createInstance(options?: HeapOptions<E, R>): this;
/**
* (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.
*/
protected _createLike<EM, RM>(elements?: Iterable<EM> | Iterable<RM>, options?: HeapOptions<EM, RM>): Heap<EM, RM>;
/**
* (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.
*/
protected _spawnLike<EM, RM>(options?: HeapOptions<EM, RM>): Heap<EM, RM>;
}
/**
* Node container used by FibonacciHeap.
* @remarks Time O(1), Space O(1)
* @template E
*/
export declare class FibonacciHeapNode<E> {
element: E;
degree: number;
left?: FibonacciHeapNode<E>;
right?: FibonacciHeapNode<E>;
child?: FibonacciHeapNode<E>;
parent?: FibonacciHeapNode<E>;
marked: boolean;
constructor(element: E, degree?: number);
}
/**
* 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
*/
export declare class FibonacciHeap<E> {
/**
* 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?: Comparator<E>);
protected _root?: FibonacciHeapNode<E>;
/**
* Get the circular root list head.
* @remarks Time O(1), Space O(1)
* @returns Root node or undefined.
*/
get root(): FibonacciHeapNode<E> | undefined;
protected _size: number;
get size(): number;
protected _min?: FibonacciHeapNode<E>;
/**
* Get the current minimum node.
* @remarks Time O(1), Space O(1)
* @returns Min node or undefined.
*/
get min(): FibonacciHeapNode<E> | undefined;
protected readonly _comparator: Comparator<E>;
get comparator(): Comparator<E>;
clear(): void;
add(element: E): boolean;
/**
* Push an element into the root list.
* @remarks Time O(1) amortized, Space O(1)
* @param element - Element to insert.
* @returns True when the element is added.
*/
push(element: E): boolean;
peek(): E | 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?: FibonacciHeapNode<E>): FibonacciHeapNode<E>[];
/**
* 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: FibonacciHeapNode<E>, node: FibonacciHeapNode<E>): void;
poll(): E | undefined;
/**
* 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(): E | undefined;
/**
* 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: FibonacciHeap<E>): void;
createNode(element: E): FibonacciHeapNode<E>;
isEmpty(): boolean;
protected _defaultComparator(a: E, b: E): number;
protected mergeWithRoot(node: FibonacciHeapNode<E>): void;
protected removeFromRoot(node: FibonacciHeapNode<E>): void;
protected _link(y: FibonacciHeapNode<E>, x: FibonacciHeapNode<E>): void;
protected _consolidate(): void;
}