Skip to content

Commit

Permalink
feat(adjacency): add FloydWarshall shortest-path impl
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Dec 21, 2022
1 parent f19c5d4 commit 26fa3ac
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/adjacency/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@
"./disjoint-set": {
"default": "./disjoint-set.js"
},
"./floyd-warshall": {
"default": "./floyd-warshall.js"
},
"./list": {
"default": "./list.js"
},
Expand Down
114 changes: 114 additions & 0 deletions packages/adjacency/src/floyd-warshall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { outOfBounds } from "@thi.ng/errors/out-of-bounds";
import type { CostFn, IGraph } from "./api.js";

/**
* Implementation of the Floyd-Warshall algorithm for finding _all_ shortest
* paths in a directed graph, optionally with positive or negative edge weights.
* A single execution of the algorithm will find the lengths (summed weights) of
* shortest paths between all pairs of vertices.
*
* @remarks
* The default cost function is topological distance (i.e. every edge has a
* length/cost of 1).
*
* Paths & accumulated distances can be queried via {@link FloydWarshall.path}
* and {@link FloydWarshall.distance}.
*
* This algorithm is quite memory hungry and requires `|V| * |V| * 8 bytes`,
* i.e. ~8MB for a graph with 1000 nodes. If possible, use {@link BFS} to
* perform individual shortest-path queries (rather than this global approach).
*
* Reference:
* - https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
*/
export class FloydWarshall {
dist: Float32Array;
next: Int32Array;
numV: number;

/**
* Instantiates and pre-computes all shortest paths in given `graph`. See
* class comments for details.
*
* @param graph
* @param cost
*/
constructor(graph: IGraph<number>, cost: CostFn = () => 1) {
const numV = (this.numV = graph.numVertices());
const dist = (this.dist = new Float32Array(numV * numV).fill(Infinity));
const next = (this.next = new Int32Array(numV * numV).fill(-1));
for (let [u, v] of graph.edges()) {
const idx = u * numV + v;
dist[idx] = cost(u, v);
next[idx] = v;
}
for (let v = 0; v < numV; v++) {
const idx = v * numV + v;
dist[idx] = 0;
next[idx] = v;
}
for (let k = 0; k < numV; k++) {
for (let i = 0; i < numV; i++) {
const idxIK = i * numV + k;
for (let j = 0; j < numV; j++) {
const idxIJ = i * numV + j;
const idxKJ = k * numV + j;
const minD = dist[idxIK] + dist[idxKJ];
if (dist[idxIJ] > minD) {
dist[idxIJ] = minD;
next[idxIJ] = next[idxIK];
}
}
}
}
}

/**
* Returns shortest distance between vertices `a` and `b`, or `undefined` if
* no connecting path exists. Throws an error if either `a` or `b` are out
* of bounds.
*
* @param a
* @param b
*/
distance(a: number, b: number): number | undefined {
this.ensurePair(a, b);
return this.dist[a * this.numV + b];
}

/**
* Returns iterator of vertex IDs of path between `a` and `b` (if any).
* Throws an error if either `a` or `b` are out of bounds.
*
* @param a
* @param b
*/
*path(a: number, b: number) {
this.ensurePair(a, b);
const { next, numV } = this;
if (next[a * numV + b] === -1) return;
yield a;
while (a !== b) {
a = next[a * numV + b];
yield a;
}
}

protected ensureIndex(id: number) {
!(id >= 0 && id < this.numV) && outOfBounds(id);
}

protected ensurePair(a: number, b: number) {
this.ensureIndex(a);
this.ensureIndex(b);
}
}

/**
* Factory function for {@link FloydWarshall}.
*
* @param graph
* @param cost
*/
export const floydWarshall = (graph: IGraph<number>, cost?: CostFn) =>
new FloydWarshall(graph, cost);
1 change: 1 addition & 0 deletions packages/adjacency/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./api.js";
export * from "./binary.js";
export * from "./disjoint-set.js";
export * from "./floyd-warshall.js";
export * from "./list.js";
export * from "./sparse.js";

Expand Down

0 comments on commit 26fa3ac

Please sign in to comment.