terra-route
Version:
A library for routing along GeoJSON LineString networks
211 lines (183 loc) • 6.59 kB
text/typescript
import { Heap } from "./heap";
/**
* Internal node representation for the Fibonacci heap, expressed as a plain
* JavaScript object (no dedicated class).
*/
interface FibonacciNode {
key: number;
value: number;
degree: number;
parent: FibonacciNode | null;
child: FibonacciNode | null;
next: FibonacciNode; // clockwise pointer in circular list
prev: FibonacciNode; // counter‑clockwise pointer in circular list
mark: boolean; // used by decrease‑key (not implemented here)
}
/**
* Fibonacci heap that conforms to the Terra Route `Heap` interface.
* Only the heap itself is a class; each node is a lightweight object created
* inline where needed.
*/
export class FibonacciHeap implements Heap {
private minNode: FibonacciNode | null = null;
private totalNodes = 0;
/**
* Inserts a new `(key, value)` pair into the heap.
*/
public insert(key: number, value: number): void {
// ---------------- inline node creation ---------------- //
const node: FibonacciNode = {
key,
value,
degree: 0,
parent: null,
child: null,
next: null as unknown as FibonacciNode,
prev: null as unknown as FibonacciNode,
mark: false,
};
node.next = node;
node.prev = node;
// ------------------------------------------------------- //
if (this.minNode === null) {
this.minNode = node;
} else {
// splice node into the circular doubly‑linked root list right after `minNode`
node.prev = this.minNode;
node.next = this.minNode.next;
this.minNode.next.prev = node;
this.minNode.next = node;
if (node.key < this.minNode.key) {
this.minNode = node;
}
}
this.totalNodes += 1;
}
/**
* Removes and returns the value associated with the minimum key. Returns
* `null` if the heap is empty.
*/
public extractMin(): number | null {
const oldMin = this.minNode;
if (oldMin === null) {
return null;
}
// Promote each child of `oldMin` to the root list
if (oldMin.child !== null) {
let childStart = oldMin.child;
const children: FibonacciNode[] = [];
do {
children.push(childStart);
childStart = childStart.next;
} while (childStart !== oldMin.child);
for (const child of children) {
// detach from child list
child.parent = null;
child.prev.next = child.next;
child.next.prev = child.prev;
// add to root list right after `oldMin`
child.prev = this.minNode!;
child.next = this.minNode!.next;
this.minNode!.next.prev = child;
this.minNode!.next = child;
}
}
// Remove `oldMin` from root list
oldMin.prev.next = oldMin.next;
oldMin.next.prev = oldMin.prev;
if (oldMin === oldMin.next) {
// The heap had exactly one root node and is now empty
this.minNode = null;
} else {
this.minNode = oldMin.next;
this.consolidate();
}
this.totalNodes -= 1;
return oldMin.value;
}
/** Returns the number of elements stored in the heap. */
public size(): number {
return this.totalNodes;
}
// --------------------------- internal helpers --------------------------- //
/**
* Merges trees of equal degree so that at most one root of each degree
* remains. Runs in `O(log n)` time and is called after `extractMin`.
*/
private consolidate(): void {
if (this.minNode === null) {
return;
}
// upper bound for the degree of any node is ⌊log₂ n⌋
const upperBound = Math.floor(Math.log2(this.totalNodes)) + 2;
const degreeTable: Array<FibonacciNode | null> = new Array(upperBound).fill(null);
// snapshot of the current root list prior to mutation
const rootList: FibonacciNode[] = [];
let currentRoot = this.minNode;
do {
rootList.push(currentRoot);
currentRoot = currentRoot.next;
} while (currentRoot !== this.minNode);
for (const root of rootList) {
let x = root;
let degree = x.degree;
while (degreeTable[degree] !== null) {
let y = degreeTable[degree] as FibonacciNode;
if (y.key < x.key) {
// ensure `x` has the smaller key
const temporary = x;
x = y;
y = temporary;
}
this.link(y, x);
degreeTable[degree] = null;
degree += 1;
}
degreeTable[degree] = x;
}
// rebuild root list and identify new minimum
this.minNode = null;
for (const node of degreeTable) {
if (node === null) {
continue;
}
if (this.minNode === null) {
this.minNode = node;
node.prev = node;
node.next = node;
} else {
// insert `node` right after current min
node.prev = this.minNode;
node.next = this.minNode.next;
this.minNode.next.prev = node;
this.minNode.next = node;
if (node.key < this.minNode.key) {
this.minNode = node;
}
}
}
}
/**
* Makes `child` a child of `parent`. Assumes both nodes are roots of equal
* degree. Runs in `O(1)` time.
*/
private link(child: FibonacciNode, parent: FibonacciNode): void {
// detach `child` from the root list
child.prev.next = child.next;
child.next.prev = child.prev;
// make `child` a child of `parent`
child.parent = parent;
child.mark = false;
if (parent.child === null) {
parent.child = child;
child.prev = child;
child.next = child;
} else {
child.prev = parent.child;
child.next = parent.child.next;
parent.child.next.prev = child;
parent.child.next = child;
}
parent.degree += 1;
}
}