max-priority-queue-typed
Version:
Max Priority Queue
1,082 lines (965 loc) • 34.5 kB
text/typescript
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
import type { DijkstraResult, EntryCallback, GraphOptions, VertexKey } from '../../types';
import { uuidV4 } from '../../utils';
import { IterableEntryBase } from '../base';
import { IGraph } from '../../interfaces';
import { Heap } from '../heap';
import { Queue } from '../queue';
export abstract class AbstractVertex<V = any> {
key: VertexKey;
value: V | undefined;
protected constructor(key: VertexKey, value?: V) {
this.key = key;
this.value = value;
}
}
export abstract class AbstractEdge<E = any> {
value: E | undefined;
weight: number;
protected constructor(weight?: number, value?: E) {
this.weight = weight !== undefined ? weight : 1;
this.value = value;
this._hashCode = uuidV4();
}
protected _hashCode: string;
get hashCode(): string {
return this._hashCode;
}
}
/**
* Abstract graph over vertices and edges.
* @template V - Vertex value type.
* @template E - Edge value type.
* @template VO - Concrete vertex subclass (extends AbstractVertex<V>).
* @template EO - Concrete edge subclass (extends AbstractEdge<E>).
* @remarks Time O(1), Space O(1)
* @example examples will be generated by unit test
*/
export abstract class AbstractGraph<
V = any,
E = any,
VO extends AbstractVertex<V> = AbstractVertex<V>,
EO extends AbstractEdge<E> = AbstractEdge<E>
>
extends IterableEntryBase<VertexKey, V | undefined>
implements IGraph<V, E, VO, EO>
{
/**
* Construct a graph with runtime defaults.
* @param options - `GraphOptions<V>` in `options.graph` (e.g. `vertexValueInitializer`, `defaultEdgeWeight`).
* @remarks Time O(1), Space O(1)
*/
constructor(options?: Partial<Record<string, unknown>>) {
super();
const graph = (options as any)?.graph as GraphOptions<V> | undefined;
this._options = { defaultEdgeWeight: 1, ...(graph ?? {}) };
}
protected _options: GraphOptions<V> = { defaultEdgeWeight: 1 };
get options(): Readonly<GraphOptions<V>> {
return this._options;
}
protected _vertexMap: Map<VertexKey, VO> = new Map<VertexKey, VO>();
get vertexMap(): Map<VertexKey, VO> {
return this._vertexMap;
}
set vertexMap(v: Map<VertexKey, VO>) {
this._vertexMap = v;
}
get size(): number {
return this._vertexMap.size;
}
/**
* Create a new vertex instance (implementation specific).
* @param key - Vertex identifier.
* @param value - Optional payload.
* @returns Concrete vertex instance.
* @remarks Time O(1), Space O(1)
*/
abstract createVertex(key: VertexKey, value?: V): VO;
/**
* Create a new edge instance (implementation specific).
* @param srcOrV1 - Source/endpoint A key.
* @param destOrV2 - Destination/endpoint B key.
* @param weight - Edge weight (defaults may apply).
* @param value - Edge payload.
* @returns Concrete edge instance.
* @remarks Time O(1), Space O(1)
*/
abstract createEdge(srcOrV1: VertexKey, destOrV2: VertexKey, weight?: number, value?: E): EO;
/**
* Delete an edge by instance.
* @param edge - Edge instance.
* @returns Removed edge or `undefined`.
* @remarks Time O(1) avg, Space O(1)
*/
abstract deleteEdge(edge: EO): EO | undefined;
/**
* Get an edge between two vertices if present.
* @param srcOrKey - Source/endpoint A vertex or key.
* @param destOrKey - Destination/endpoint B vertex or key.
* @returns Edge instance or `undefined`.
* @remarks Time O(1) avg, Space O(1)
*/
abstract getEdge(srcOrKey: VO | VertexKey, destOrKey: VO | VertexKey): EO | undefined;
/**
* Degree of a vertex in this graph model.
* @param vertexOrKey - Vertex or key.
* @returns Non-negative integer degree.
* @remarks Time O(1) avg, Space O(1)
*/
abstract degreeOf(vertexOrKey: VO | VertexKey): number;
/**
* All edges in the graph (unique, order not guaranteed).
* @returns Array of edges.
* @remarks Time O(E), Space O(E)
*/
abstract edgeSet(): EO[];
/**
* Incident edges of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Array of incident edges.
* @remarks Time O(deg), Space O(deg)
*/
abstract edgesOf(vertexOrKey: VO | VertexKey): EO[];
/**
* One-step neighbors of a vertex.
* @param vertexOrKey - Vertex or key.
* @returns Array of neighbor vertices.
* @remarks Time O(deg), Space O(deg)
*/
abstract getNeighbors(vertexOrKey: VO | VertexKey): VO[];
/**
* Resolve endpoints of an edge to vertex instances.
* @param edge - Edge instance.
* @returns `[v1, v2]` or `undefined` if missing.
* @remarks Time O(1), Space O(1)
*/
abstract getEndsOfEdge(edge: EO): [VO, VO] | undefined;
/**
* Get vertex instance by key.
* @param vertexKey - Vertex key.
* @returns Vertex instance or `undefined`.
* @remarks Time O(1), Space O(1)
*/
getVertex(vertexKey: VertexKey): VO | undefined {
return this._vertexMap.get(vertexKey) || undefined;
}
/**
* Whether a vertex exists.
* @param vertexOrKey - Vertex or key.
* @returns `true` if present, otherwise `false`.
* @remarks Time O(1) avg, Space O(1)
*/
hasVertex(vertexOrKey: VO | VertexKey): boolean {
return this._vertexMap.has(this._getVertexKey(vertexOrKey));
}
addVertex(vertex: VO): boolean;
addVertex(key: VertexKey, value?: V): boolean;
/**
* Add a vertex by key/value or by pre-built vertex.
* @param keyOrVertex - Vertex key or existing vertex instance.
* @param value - Optional payload.
* @returns `true` if inserted; `false` when key already exists.
* @remarks Time O(1) avg, Space O(1)
*/
addVertex(keyOrVertex: VertexKey | VO, value?: V): boolean {
if (keyOrVertex instanceof AbstractVertex) {
return this._addVertex(keyOrVertex);
} else {
const newVertex = this.createVertex(keyOrVertex, value);
return this._addVertex(newVertex);
}
}
/**
* Type guard: check if a value is a valid vertex key.
* @param potentialKey - Value to test.
* @returns `true` if string/number; else `false`.
* @remarks Time O(1), Space O(1)
*/
isVertexKey(potentialKey: any): potentialKey is VertexKey {
const potentialKeyType = typeof potentialKey;
return potentialKeyType === 'string' || potentialKeyType === 'number';
}
/**
* Delete a vertex and its incident edges.
* @param vertexOrKey - Vertex or key.
* @returns `true` if removed; otherwise `false`.
* @remarks Time O(deg), Space O(1)
*/
abstract deleteVertex(vertexOrKey: VO | VertexKey): boolean;
/**
* Delete multiple vertices.
* @param vertexMap - Array of vertices or keys.
* @returns `true` if any vertex was removed.
* @remarks Time O(sum(deg)), Space O(1)
*/
removeManyVertices(vertexMap: VO[] | VertexKey[]): boolean {
const removed: boolean[] = [];
for (const v of vertexMap) {
removed.push(this.deleteVertex(v));
}
return removed.length > 0;
}
/**
* Whether an edge exists between two vertices.
* @param v1 - Endpoint A vertex or key.
* @param v2 - Endpoint B vertex or key.
* @returns `true` if present; otherwise `false`.
* @remarks Time O(1) avg, Space O(1)
*/
hasEdge(v1: VertexKey | VO, v2: VertexKey | VO): boolean {
const edge = this.getEdge(v1, v2);
return !!edge;
}
addEdge(edge: EO): boolean;
addEdge(src: VO | VertexKey, dest: VO | VertexKey, weight?: number, value?: E): boolean;
/**
* Add an edge by instance or by `(src, dest, weight?, value?)`.
* @param srcOrEdge - Edge instance or source vertex/key.
* @param dest - Destination vertex/key (when adding by pair).
* @param weight - Edge weight.
* @param value - Edge payload.
* @returns `true` if inserted; otherwise `false`.
* @remarks Time O(1) avg, Space O(1)
*/
addEdge(srcOrEdge: VO | VertexKey | EO, dest?: VO | VertexKey, weight?: number, value?: E): boolean {
if (srcOrEdge instanceof AbstractEdge) {
return this._addEdge(srcOrEdge);
} else {
if (dest instanceof AbstractVertex || typeof dest === 'string' || typeof dest === 'number') {
if (!(this.hasVertex(srcOrEdge) && this.hasVertex(dest))) return false;
if (srcOrEdge instanceof AbstractVertex) srcOrEdge = srcOrEdge.key;
if (dest instanceof AbstractVertex) dest = dest.key;
const newEdge = this.createEdge(srcOrEdge, dest, weight, value);
return this._addEdge(newEdge);
} else {
throw new Error('dest must be a Vertex or vertex key while srcOrEdge is an Edge');
}
}
}
/**
* Set the weight of an existing edge.
* @param srcOrKey - Source vertex or key.
* @param destOrKey - Destination vertex or key.
* @param weight - New weight.
* @returns `true` if updated; otherwise `false`.
* @remarks Time O(1) avg, Space O(1)
*/
setEdgeWeight(srcOrKey: VertexKey | VO, destOrKey: VertexKey | VO, weight: number): boolean {
const edge = this.getEdge(srcOrKey, destOrKey);
if (edge) {
edge.weight = weight;
return true;
} else {
return false;
}
}
/**
* Enumerate simple paths up to a limit.
* @param v1 - Source vertex or key.
* @param v2 - Destination vertex or key.
* @param limit - Maximum number of paths to collect.
* @returns Array of paths (each path is an array of vertices).
* @remarks Time O(paths) worst-case exponential, Space O(V + paths)
*/
getAllPathsBetween(v1: VO | VertexKey, v2: VO | VertexKey, limit = 1000): VO[][] {
const paths: VO[][] = [];
const vertex1 = this._getVertex(v1);
const vertex2 = this._getVertex(v2);
if (!(vertex1 && vertex2)) {
return [];
}
const stack: { vertex: VO; path: VO[] }[] = [];
stack.push({ vertex: vertex1, path: [vertex1] });
while (stack.length > 0) {
const { vertex, path } = stack.pop()!;
if (vertex === vertex2) {
paths.push(path);
if (paths.length >= limit) return paths;
}
const neighbors = this.getNeighbors(vertex);
for (const neighbor of neighbors) {
if (!path.includes(neighbor)) {
const newPath = [...path, neighbor];
stack.push({ vertex: neighbor, path: newPath });
}
}
}
return paths;
}
/**
* Sum the weights along a vertex path.
* @param path - Sequence of vertices.
* @returns Path weight sum (0 if empty or edge missing).
* @remarks Time O(L), Space O(1) where L is path length
*/
getPathSumWeight(path: VO[]): number {
let sum = 0;
for (let i = 0; i < path.length; i++) {
sum += this.getEdge(path[i], path[i + 1])?.weight || 0;
}
return sum;
}
/**
* Minimum hops/weight between two vertices.
* @param v1 - Source vertex or key.
* @param v2 - Destination vertex or key.
* @param isWeight - If `true`, compare by path weight; otherwise by hop count.
* @returns Minimum cost or `undefined` if missing/unreachable.
* @remarks Time O((V + E) log V) weighted / O(V + E) unweighted, Space O(V + E)
*/
getMinCostBetween(v1: VO | VertexKey, v2: VO | VertexKey, isWeight?: boolean): number | undefined {
if (isWeight === undefined) isWeight = false;
if (isWeight) {
const allPaths = this.getAllPathsBetween(v1, v2);
let min = Number.MAX_SAFE_INTEGER;
for (const path of allPaths) {
min = Math.min(this.getPathSumWeight(path), min);
}
return min;
} else {
const vertex2 = this._getVertex(v2);
const vertex1 = this._getVertex(v1);
if (!(vertex1 && vertex2)) {
return undefined;
}
const visited: Map<VO, boolean> = new Map();
const queue = new Queue<VO>([vertex1]);
visited.set(vertex1, true);
let cost = 0;
while (queue.length > 0) {
for (let i = 0, layerSize = queue.length; i < layerSize; i++) {
const cur = queue.shift();
if (cur === vertex2) {
return cost;
}
if (cur !== undefined) {
const neighbors = this.getNeighbors(cur);
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
visited.set(neighbor, true);
queue.push(neighbor);
}
}
}
}
cost++;
}
return undefined;
}
}
/**
* Minimum path (as vertex sequence) between two vertices.
* @param v1 - Source vertex or key.
* @param v2 - Destination vertex or key.
* @param isWeight - If `true`, compare by path weight; otherwise by hop count.
* @param isDFS - For weighted mode only: if `true`, brute-force all paths; if `false`, use Dijkstra.
* @returns Vertex sequence, or `undefined`/empty when unreachable depending on branch.
* @remarks Time O((V + E) log V) weighted / O(V + E) unweighted, Space O(V + E)
*/
getMinPathBetween(v1: VO | VertexKey, v2: VO | VertexKey, isWeight?: boolean, isDFS = false): VO[] | undefined {
if (isWeight === undefined) isWeight = false;
if (isWeight) {
if (isDFS) {
const allPaths = this.getAllPathsBetween(v1, v2, 10000);
let min = Number.MAX_SAFE_INTEGER;
let minIndex = -1;
let index = 0;
for (const path of allPaths) {
const pathSumWeight = this.getPathSumWeight(path);
if (pathSumWeight < min) {
min = pathSumWeight;
minIndex = index;
}
index++;
}
return allPaths[minIndex] || undefined;
} else {
/**
* Dijkstra (binary-heap) shortest paths for non-negative weights.
* @param src - Source vertex or key.
* @param dest - Optional destination for early stop.
* @param getMinDist - If `true`, compute global minimum distance.
* @param genPaths - If `true`, also generate path arrays.
* @returns Result bag or `undefined` if source missing.
* @remarks Time O((V + E) log V), Space O(V + E)
*/
return this.dijkstra(v1, v2, true, true)?.minPath ?? [];
}
} else {
let minPath: VO[] = [];
const vertex1 = this._getVertex(v1);
const vertex2 = this._getVertex(v2);
if (!(vertex1 && vertex2)) return [];
const dfs = (cur: VO, dest: VO, visiting: Set<VO>, path: VO[]) => {
visiting.add(cur);
if (cur === dest) {
minPath = [vertex1, ...path];
return;
}
const neighbors = this.getNeighbors(cur);
for (const neighbor of neighbors) {
if (!visiting.has(neighbor)) {
path.push(neighbor);
dfs(neighbor, dest, visiting, path);
path.pop();
}
}
visiting.delete(cur);
};
dfs(vertex1, vertex2, new Set<VO>(), []);
return minPath;
}
}
/**
* Dijkstra without heap (array-based selection).
* @param src - Source vertex or key.
* @param dest - Optional destination for early stop.
* @param getMinDist - If `true`, compute global minimum distance.
* @param genPaths - If `true`, also generate path arrays.
* @returns Result bag or `undefined` if source missing.
* @remarks Time O(V^2 + E), Space O(V + E)
*/
dijkstraWithoutHeap(
src: VO | VertexKey,
dest: VO | VertexKey | undefined = undefined,
getMinDist: boolean = false,
genPaths: boolean = false
): DijkstraResult<VO> | undefined {
let minDist = Number.MAX_SAFE_INTEGER;
let minDest: VO | undefined = undefined;
let minPath: VO[] = [];
const paths: VO[][] = [];
const vertexMap = this._vertexMap;
const distMap: Map<VO, number> = new Map();
const seen: Set<VO> = new Set();
const preMap: Map<VO, VO | undefined> = new Map();
const srcVertex = this._getVertex(src);
const destVertex = dest ? this._getVertex(dest) : undefined;
if (!srcVertex) {
return undefined;
}
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Number.MAX_SAFE_INTEGER);
}
distMap.set(srcVertex, 0);
preMap.set(srcVertex, undefined);
const getMinOfNoSeen = () => {
let min = Number.MAX_SAFE_INTEGER;
let minV: VO | undefined = undefined;
for (const [key, value] of distMap) {
if (!seen.has(key)) {
if (value < min) {
min = value;
minV = key;
}
}
}
return minV;
};
const getPaths = (minV: VO | undefined) => {
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) {
const path: VO[] = [vertexOrKey];
let parent = preMap.get(vertexOrKey);
while (parent) {
path.push(parent);
parent = preMap.get(parent);
}
const reversed = path.reverse();
if (vertex[1] === minV) minPath = reversed;
paths.push(reversed);
}
}
};
for (let i = 1; i < vertexMap.size; i++) {
const cur = getMinOfNoSeen();
if (cur) {
seen.add(cur);
if (destVertex && destVertex === cur) {
if (getMinDist) {
minDist = distMap.get(destVertex) || Number.MAX_SAFE_INTEGER;
}
if (genPaths) {
getPaths(destVertex);
}
return { distMap, preMap, seen, paths, minDist, minPath };
}
const neighbors = this.getNeighbors(cur);
for (const neighbor of neighbors) {
if (!seen.has(neighbor)) {
const edge = this.getEdge(cur, neighbor);
if (edge) {
const curFromMap = distMap.get(cur);
const neighborFromMap = distMap.get(neighbor);
if (curFromMap !== undefined && neighborFromMap !== undefined) {
if (edge.weight + curFromMap < neighborFromMap) {
distMap.set(neighbor, edge.weight + curFromMap);
preMap.set(neighbor, cur);
}
}
}
}
}
}
}
if (getMinDist)
distMap.forEach((d, v) => {
if (v !== srcVertex) {
if (d < minDist) {
minDist = d;
if (genPaths) minDest = v;
}
}
});
if (genPaths) getPaths(minDest);
return { distMap, preMap, seen, paths, minDist, minPath };
}
dijkstra(
src: VO | VertexKey,
dest: VO | VertexKey | undefined = undefined,
getMinDist: boolean = false,
genPaths: boolean = false
): DijkstraResult<VO> | undefined {
let minDist = Number.MAX_SAFE_INTEGER;
let minDest: VO | undefined = undefined;
let minPath: VO[] = [];
const paths: VO[][] = [];
const vertexMap = this._vertexMap;
const distMap: Map<VO, number> = new Map();
const seen: Set<VO> = new Set();
const preMap: Map<VO, VO | undefined> = new Map();
const srcVertex = this._getVertex(src);
const destVertex = dest ? this._getVertex(dest) : undefined;
if (!srcVertex) return undefined;
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) distMap.set(vertexOrKey, Number.MAX_SAFE_INTEGER);
}
const heap = new Heap<{ key: number; value: VO }>([], { comparator: (a, b) => a.key - b.key });
heap.add({ key: 0, value: srcVertex });
distMap.set(srcVertex, 0);
preMap.set(srcVertex, undefined);
const getPaths = (minV: VO | undefined) => {
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) {
const path: VO[] = [vertexOrKey];
let parent = preMap.get(vertexOrKey);
while (parent) {
path.push(parent);
parent = preMap.get(parent);
}
const reversed = path.reverse();
if (vertex[1] === minV) minPath = reversed;
paths.push(reversed);
}
}
};
while (heap.size > 0) {
const curHeapNode = heap.poll();
const dist = curHeapNode?.key;
const cur = curHeapNode?.value;
if (dist !== undefined) {
if (cur) {
seen.add(cur);
if (destVertex && destVertex === cur) {
if (getMinDist) {
minDist = distMap.get(destVertex) || Number.MAX_SAFE_INTEGER;
}
if (genPaths) {
getPaths(destVertex);
}
return { distMap, preMap, seen, paths, minDist, minPath };
}
const neighbors = this.getNeighbors(cur);
for (const neighbor of neighbors) {
if (!seen.has(neighbor)) {
const weight = this.getEdge(cur, neighbor)?.weight;
if (typeof weight === 'number') {
const distSrcToNeighbor = distMap.get(neighbor);
if (distSrcToNeighbor !== undefined) {
if (dist + weight < distSrcToNeighbor) {
heap.add({ key: dist + weight, value: neighbor });
preMap.set(neighbor, cur);
distMap.set(neighbor, dist + weight);
}
}
}
}
}
}
}
}
if (getMinDist) {
distMap.forEach((d, v) => {
if (v !== srcVertex) {
if (d < minDist) {
minDist = d;
if (genPaths) minDest = v;
}
}
});
}
if (genPaths) {
getPaths(minDest);
}
return { distMap, preMap, seen, paths, minDist, minPath };
}
/**
* Bellman-Ford single-source shortest paths with option to scan negative cycles.
* @param src - Source vertex or key.
* @param scanNegativeCycle - If `true`, also detect negative cycles.
* @param getMin - If `true`, compute global minimum distance.
* @param genPath - If `true`, generate path arrays via predecessor map.
* @returns Result bag including distances, predecessors, and optional cycle flag.
* @remarks Time O(V * E), Space O(V + E)
*/
bellmanFord(src: VO | VertexKey, scanNegativeCycle?: boolean, getMin?: boolean, genPath?: boolean) {
if (getMin === undefined) getMin = false;
if (genPath === undefined) genPath = false;
const srcVertex = this._getVertex(src);
const paths: VO[][] = [];
const distMap: Map<VO, number> = new Map();
const preMap: Map<VO, VO> = new Map();
let min = Number.MAX_SAFE_INTEGER;
let minPath: VO[] = [];
let hasNegativeCycle: boolean | undefined;
if (scanNegativeCycle) hasNegativeCycle = false;
if (!srcVertex) return { hasNegativeCycle, distMap, preMap, paths, min, minPath };
const vertexMap = this._vertexMap;
const numOfVertices = vertexMap.size;
const edgeMap = this.edgeSet();
const numOfEdges = edgeMap.length;
this._vertexMap.forEach(vertex => {
distMap.set(vertex, Number.MAX_SAFE_INTEGER);
});
distMap.set(srcVertex, 0);
for (let i = 1; i < numOfVertices; ++i) {
for (let j = 0; j < numOfEdges; ++j) {
const ends = this.getEndsOfEdge(edgeMap[j]);
if (ends) {
const [s, d] = ends;
const weight = edgeMap[j].weight;
const sWeight = distMap.get(s);
const dWeight = distMap.get(d);
if (sWeight !== undefined && dWeight !== undefined) {
if (distMap.get(s) !== Number.MAX_SAFE_INTEGER && sWeight + weight < dWeight) {
distMap.set(d, sWeight + weight);
if (genPath) preMap.set(d, s);
}
}
}
}
}
let minDest: VO | undefined = undefined;
if (getMin) {
distMap.forEach((d, v) => {
if (v !== srcVertex) {
if (d < min) {
min = d;
if (genPath) minDest = v;
}
}
});
}
if (genPath) {
for (const vertex of vertexMap) {
const vertexOrKey = vertex[1];
if (vertexOrKey instanceof AbstractVertex) {
const path: VO[] = [vertexOrKey];
let parent = preMap.get(vertexOrKey);
while (parent !== undefined) {
path.push(parent);
parent = preMap.get(parent);
}
const reversed = path.reverse();
if (vertex[1] === minDest) minPath = reversed;
paths.push(reversed);
}
}
}
for (let j = 0; j < numOfEdges; ++j) {
const ends = this.getEndsOfEdge(edgeMap[j]);
if (ends) {
const [s] = ends;
const weight = edgeMap[j].weight;
const sWeight = distMap.get(s);
if (sWeight) {
if (sWeight !== Number.MAX_SAFE_INTEGER && sWeight + weight < sWeight) hasNegativeCycle = true;
}
}
}
return { hasNegativeCycle, distMap, preMap, paths, min, minPath };
}
/**
* Floyd–Warshall all-pairs shortest paths.
* @returns `{ costs, predecessor }` matrices.
* @remarks Time O(V^3), Space O(V^2)
*/
floydWarshall(): { costs: number[][]; predecessor: (VO | undefined)[][] } {
const idAndVertices = [...this._vertexMap];
const n = idAndVertices.length;
const costs: number[][] = [];
const predecessor: (VO | undefined)[][] = [];
for (let i = 0; i < n; i++) {
costs[i] = [];
predecessor[i] = [];
for (let j = 0; j < n; j++) {
predecessor[i][j] = undefined;
}
}
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
costs[i][j] = this.getEdge(idAndVertices[i][1], idAndVertices[j][1])?.weight || Number.MAX_SAFE_INTEGER;
}
}
for (let k = 0; k < n; k++) {
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (costs[i][j] > costs[i][k] + costs[k][j]) {
costs[i][j] = costs[i][k] + costs[k][j];
predecessor[i][j] = idAndVertices[k][1];
}
}
}
}
return { costs, predecessor };
}
/**
* Enumerate simple cycles (may be expensive).
* @param isInclude2Cycle - If `true`, include 2-cycles when graph semantics allow.
* @returns Array of cycles (each as array of vertex keys).
* @remarks Time exponential in worst-case, Space O(V + E)
*/
getCycles(isInclude2Cycle: boolean = false): VertexKey[][] {
const cycles: VertexKey[][] = [];
const visited: Set<VO> = new Set();
const dfs = (vertex: VO, currentPath: VertexKey[], visited: Set<VO>) => {
if (visited.has(vertex)) {
if (
((!isInclude2Cycle && currentPath.length > 2) || (isInclude2Cycle && currentPath.length >= 2)) &&
currentPath[0] === vertex.key
) {
cycles.push([...currentPath]);
}
return;
}
visited.add(vertex);
currentPath.push(vertex.key);
for (const neighbor of this.getNeighbors(vertex)) {
if (neighbor) dfs(neighbor, currentPath, visited);
}
visited.delete(vertex);
currentPath.pop();
};
for (const vertex of this.vertexMap.values()) {
dfs(vertex, [], visited);
}
const uniqueCycles = new Map<string, VertexKey[]>();
for (const cycle of cycles) {
const sorted = [...cycle].sort().toString();
if (uniqueCycles.has(sorted)) continue;
else {
uniqueCycles.set(sorted, cycle);
}
}
/**
* Map entries to an array via callback.
* @param callback - `(key, value, index, self) => T`.
* @param thisArg - Optional `this` for callback.
* @returns Mapped results.
* @remarks Time O(V), Space O(V)
*/
return [...uniqueCycles].map(cycleString => cycleString[1]);
}
/**
* Induced-subgraph filter: keep vertices where `predicate(key, value)` is true,
* and only keep edges whose endpoints both survive.
* @param predicate - `(key, value, index, self) => boolean`.
* @param thisArg - Optional `this` for callback.
* @returns A new graph of the same concrete class (`this` type).
* @remarks Time O(V + E), Space O(V + E)
*/
filter(predicate: EntryCallback<VertexKey, V | undefined, boolean>, thisArg?: any): this {
const filtered: [VertexKey, V | undefined][] = [];
let index = 0;
for (const [key, value] of this) {
if (predicate.call(thisArg, key, value, index, this)) {
filtered.push([key, value]);
}
index++;
}
return this._createLike(filtered, this._snapshotOptions());
}
/**
* Preserve the old behavior: return filtered entries as an array.
* @remarks Time O(V), Space O(V)
*/
filterEntries(
predicate: EntryCallback<VertexKey, V | undefined, boolean>,
thisArg?: any
): [VertexKey, V | undefined][] {
const filtered: [VertexKey, V | undefined][] = [];
let index = 0;
for (const [key, value] of this) {
if (predicate.call(thisArg, key, value, index, this)) {
filtered.push([key, value]);
}
index++;
}
return filtered;
}
map<T>(callback: EntryCallback<VertexKey, V | undefined, T>, thisArg?: any): T[] {
const mapped: T[] = [];
let index = 0;
for (const [key, value] of this) {
mapped.push(callback.call(thisArg, key, value, index, this));
index++;
}
return mapped;
}
/**
* Create a deep clone of the graph with the same species.
* @remarks Time O(V + E), Space O(V + E)
*/
/**
* Create a deep clone of the graph with the same species.
* @returns A new graph of the same concrete class (`this` type).
* @remarks Time O(V + E), Space O(V + E)
*/
clone(): this {
return this._createLike(undefined, this._snapshotOptions());
}
// ===== Same-species factory & cloning helpers =====
/**
* Internal iterator over `[key, value]` entries in insertion order.
* @returns Iterator of `[VertexKey, V | undefined]`.
* @remarks Time O(V), Space O(1)
*/
protected *_getIterator(): IterableIterator<[VertexKey, V | undefined]> {
for (const vertex of this._vertexMap.values()) {
yield [vertex.key, vertex.value];
}
}
/**
* Capture configuration needed to reproduce the current graph.
* Currently the graph has no runtime options, so we return an empty object.
*/
/**
* Capture configuration needed to reproduce the current graph.
* @returns Options bag (opaque to callers).
* @remarks Time O(1), Space O(1)
*/
protected _snapshotOptions(): Record<string, unknown> {
return { graph: { ...this._options } };
}
/**
* Create an empty graph instance of the same concrete species (Directed/Undirected/etc).
* @remarks Time O(1), Space O(1)
*/
/**
* Create an empty graph instance of the same concrete species.
* @param _options - Snapshot options from `_snapshotOptions()`.
* @returns A new empty graph instance of `this` type.
* @remarks Time O(1), Space O(1)
*/
protected _createInstance(_options?: Partial<Record<string, unknown>>): this {
const Ctor: any = (this as any).constructor;
const instance: this = new Ctor();
const graph = (_options as any)?.graph as GraphOptions<V> | undefined;
if (graph) (instance as any)._options = { ...(instance as any)._options, ...graph };
else (instance as any)._options = { ...(instance as any)._options, ...(this as any)._options };
return instance;
}
/**
* Create a same-species graph populated with the given entries.
* Also preserves edges between kept vertices from the source graph.
* @remarks Time O(V + E), Space O(V + E)
*/
/**
* Create a same-species graph populated with entries; preserves edges among kept vertices.
* @param iter - Optional entries to seed the new graph.
* @param options - Snapshot options.
* @returns A new graph of `this` type.
* @remarks Time O(V + E), Space O(V + E)
*/
protected _createLike(iter?: Iterable<[VertexKey, V | undefined]>, options?: Partial<Record<string, unknown>>): this {
const g = this._createInstance(options);
// 1) Add vertices
if (iter) {
for (const [k, v] of iter) {
(g as any).addVertex(k as VertexKey, v as V | undefined);
}
} else {
for (const [k, v] of this) {
(g as any).addVertex(k as VertexKey, v as V | undefined);
}
}
// 2) Add edges whose endpoints exist in the new graph
const edges = this.edgeSet();
for (const e of edges as any[]) {
const ends = this.getEndsOfEdge(e as any) as unknown as [any, any] | undefined;
if (!ends) continue;
const [va, vb] = ends;
const ka = (va as any).key as VertexKey;
const kb = (vb as any).key as VertexKey;
const hasA = (g as any).hasVertex ? (g as any).hasVertex(ka) : false;
const hasB = (g as any).hasVertex ? (g as any).hasVertex(kb) : false;
if (hasA && hasB) {
const w = (e as any).weight;
const val = (e as any).value;
const newEdge = (g as any).createEdge(ka, kb, w, val);
(g as any)._addEdge(newEdge);
}
}
return g;
}
/**
* Internal hook to attach an edge into adjacency structures.
* @param edge - Edge instance.
* @returns `true` if inserted; otherwise `false`.
* @remarks Time O(1) avg, Space O(1)
*/
protected abstract _addEdge(edge: EO): boolean;
/**
* Insert a pre-built vertex into the graph.
* @param newVertex - Concrete vertex instance.
* @returns `true` if inserted; `false` if key already exists.
* @remarks Time O(1) avg, Space O(1)
*/
protected _addVertex(newVertex: VO): boolean {
if (this.hasVertex(newVertex)) {
return false;
}
this._vertexMap.set(newVertex.key, newVertex);
return true;
}
/**
* Resolve a vertex key or instance to the concrete vertex instance.
* @param vertexOrKey - Vertex key or existing vertex.
* @returns Vertex instance or `undefined`.
* @remarks Time O(1), Space O(1)
*/
protected _getVertex(vertexOrKey: VertexKey | VO): VO | undefined {
const vertexKey = this._getVertexKey(vertexOrKey);
return this._vertexMap.get(vertexKey) || undefined;
}
/**
* Resolve a vertex key from a key or vertex instance.
* @param vertexOrKey - Vertex key or existing vertex.
* @returns The vertex key.
* @remarks Time O(1), Space O(1)
*/
protected _getVertexKey(vertexOrKey: VO | VertexKey): VertexKey {
return vertexOrKey instanceof AbstractVertex ? vertexOrKey.key : vertexOrKey;
}
}