/** * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Gianluca Tomasello * * Adapted from https://github.com/gwtw/ts-fibonacci-heap, Copyright (c) 2014 Daniel Imms, MIT */ interface INode { key: K; value?: V; } type CompareFunction = (a: INode, b: INode) => number; class Node implements INode { public key: K; public value: V | undefined; public prev: Node; public next: Node; public parent: Node | null = null; public child: Node | null = null; public degree: number = 0; public isMarked: boolean = false; constructor(key: K, value?: V) { this.key = key; this.value = value; this.prev = this; this.next = this; } } class NodeListIterator { private _index: number; private _items: Node[]; private _len: number; /** * Creates an Iterator used to simplify the consolidate() method. It works by * making a shallow copy of the nodes in the root list and iterating over the * shallow copy instead of the source as the source will be modified. * @param start A node from the root list. */ constructor(start?: Node) { this._index = -1; this._items = []; this._len = 0; if (start) { let current = start, l = 0; do { this._items[l++] = current; current = current.next; } while (start !== current); this._len = l; } } /** * @return Whether there is a next node in the iterator. */ public hasNext(): boolean { return this._index < this._len - 1; } /** * @return The next node. */ public next(): Node { return this._items[++this._index]; } /** * @return Resets iterator to reuse it. */ public reset(start: Node) { this._index = -1; this._len = 0; let current = start, l = 0; do { this._items[l++] = current; current = current.next; } while (start !== current); this._len = l; } } const tmpIt = new NodeListIterator(); /** * A Fibonacci heap data structure with a key and optional value. */ export class FibonacciHeap { private _minNode: Node | null = null; private _nodeCount: number = 0; private _compare: CompareFunction; constructor( compare?: CompareFunction ) { this._compare = compare ? compare : this._defaultCompare; } /** * Clears the heap's data, making it an empty heap. */ public clear(): void { this._minNode = null; this._nodeCount = 0; } /** * Decreases a key of a node. * @param node The node to decrease the key of. * @param newKey The new key to assign to the node. */ public decreaseKey(node: Node, newKey: K): void { if (!node) { throw new Error('Cannot decrease key of non-existent node'); } if (this._compare({ key: newKey }, { key: node.key }) > 0) { throw new Error('New key is larger than old key'); } node.key = newKey; const parent = node.parent; if (parent && this._compare(node, parent) < 0) { this._cut(node, parent, > this._minNode); this._cascadingCut(parent, > this._minNode); } if (this._compare(node, > this._minNode) < 0) { this._minNode = node; } } /** * Deletes a node. * @param node The node to delete. */ public delete(node: Node): void { // This is a special implementation of decreaseKey that sets the argument to // the minimum value. This is necessary to make generic keys work, since there // is no MIN_VALUE constant for generic types. const parent = node.parent; if (parent) { this._cut(node, parent, > this._minNode); this._cascadingCut(parent, > this._minNode); } this._minNode = node; this.extractMinimum(); } /** * Extracts and returns the minimum node from the heap. * @return The heap's minimum node or null if the heap is empty. */ public extractMinimum(): Node | null { const extractedMin = this._minNode; if (extractedMin) { // Set parent to null for the minimum's children if (extractedMin.child) { let child = extractedMin.child; do { child.parent = null; child = child.next; } while (child !== extractedMin.child); } let nextInRootList = null; if (extractedMin.next !== extractedMin) { nextInRootList = extractedMin.next; } // Remove min from root list this._removeNodeFromList(extractedMin); this._nodeCount--; // Merge the children of the minimum node with the root list this._minNode = this._mergeLists(nextInRootList, extractedMin.child); if (this._minNode) { this._minNode = this._consolidate(this._minNode); } } return extractedMin; } /** * Returns the minimum node from the heap. * @return The heap's minimum node or null if the heap is empty. */ public findMinimum(): Node | null { return this._minNode; } /** * Inserts a new key-value pair into the heap. * @param key The key to insert. * @param value The value to insert. * @return node The inserted node. */ public insert(key: K, value?: V): Node { const node = new Node(key, value); this._minNode = this._mergeLists(this._minNode, node); this._nodeCount++; return node; } /** * @return Whether the heap is empty. */ public isEmpty(): boolean { return this._minNode === null; } /** * @return The size of the heap. */ public size(): number { if (this._minNode === null) { return 0; } return this._getNodeListSize(this._minNode); } /** * Joins another heap to this heap. * @param other The other heap. */ public union(other: FibonacciHeap): void { this._minNode = this._mergeLists(this._minNode, other._minNode); this._nodeCount += other._nodeCount; } /** * Compares two nodes with each other. * @param a The first key to compare. * @param b The second key to compare. * @return -1, 0 or 1 if a < b, a == b or a > b respectively. */ private _defaultCompare(a: INode, b: INode): number { if (a.key > b.key) { return 1; } if (a.key < b.key) { return -1; } return 0; } /** * Cut the link between a node and its parent, moving the node to the root list. * @param node The node being cut. * @param parent The parent of the node being cut. * @param minNode The minimum node in the root list. * @return The heap's new minimum node. */ private _cut(node: Node, parent: Node, minNode: Node): Node | null { node.parent = null; parent.degree--; if (node.next === node) { parent.child = null; } else { parent.child = node.next; } this._removeNodeFromList(node); const newMinNode = this._mergeLists(minNode, node); node.isMarked = false; return newMinNode; } /** * Perform a cascading cut on a node; mark the node if it is not marked, * otherwise cut the node and perform a cascading cut on its parent. * @param node The node being considered to be cut. * @param minNode The minimum node in the root list. * @return The heap's new minimum node. */ private _cascadingCut(node: Node, minNode: Node | null): Node | null { const parent = node.parent; if (parent) { if (node.isMarked) { minNode = this._cut(node, parent, >minNode); minNode = this._cascadingCut(parent, minNode); } else { node.isMarked = true; } } return minNode; } /** * Merge all trees of the same order together until there are no two trees of * the same order. * @param minNode The current minimum node. * @return The new minimum node. */ private _consolidate(minNode: Node): Node | null { const aux = []; tmpIt.reset(minNode); while (tmpIt.hasNext()) { let current = tmpIt.next(); // If there exists another node with the same degree, merge them let auxCurrent = aux[current.degree]; while (auxCurrent) { if (this._compare(current, auxCurrent) > 0) { const temp = current; current = auxCurrent; auxCurrent = temp; } this._linkHeaps(auxCurrent, current); aux[current.degree] = null; current.degree++; auxCurrent = aux[current.degree]; } aux[current.degree] = current; } let newMinNode = null; for (let i = 0; i < aux.length; i++) { const node = aux[i]; if (node) { // Remove siblings before merging node.next = node; node.prev = node; newMinNode = this._mergeLists(newMinNode, node); } } return newMinNode; } /** * Removes a node from a node list. * @param node The node to remove. */ private _removeNodeFromList(node: Node): void { const prev = node.prev; const next = node.next; prev.next = next; next.prev = prev; node.next = node; node.prev = node; } /** * Links two heaps of the same order together. * * @private * @param max The heap with the larger root. * @param min The heap with the smaller root. */ private _linkHeaps(max: Node, min: Node): void { this._removeNodeFromList(max); min.child = this._mergeLists(max, min.child); max.parent = min; max.isMarked = false; } /** * Merge two lists of nodes together. * * @private * @param a The first list to merge. * @param b The second list to merge. * @return The new minimum node from the two lists. */ private _mergeLists(a: Node | null, b: Node | null): Node | null { if (!a) { if (!b) { return null; } return b; } if (!b) { return a; } const temp = a.next; a.next = b.next; a.next.prev = a; b.next = temp; b.next.prev = b; return this._compare(a, b) < 0 ? a : b; } /** * Gets the size of a node list. * @param node A node within the node list. * @return The size of the node list. */ private _getNodeListSize(node: Node): number { let count = 0; let current = node; do { count++; if (current.child) { count += this._getNodeListSize(current.child); } current = current.next; } while (current !== node); return count; } }