You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
5.3 KiB
158 lines
5.3 KiB
import {PairingHeap, PriorityQueue} from './pqueue'
|
|
|
|
class Neighbour {
|
|
constructor(public id: number, public distance: number) { }
|
|
}
|
|
|
|
class Node {
|
|
constructor(public id: number) {
|
|
this.neighbours = [];
|
|
}
|
|
neighbours: Neighbour[];
|
|
d: number;
|
|
prev: Node;
|
|
q: PairingHeap<Node>;
|
|
}
|
|
|
|
class QueueEntry {
|
|
constructor(public node: Node, public prev: QueueEntry, public d: number) {}
|
|
}
|
|
|
|
/**
|
|
* calculates all-pairs shortest paths or shortest paths from a single node
|
|
* @class Calculator
|
|
* @constructor
|
|
* @param n {number} number of nodes
|
|
* @param es {Edge[]} array of edges
|
|
*/
|
|
export class Calculator<Link> {
|
|
private neighbours: Node[];
|
|
|
|
constructor(public n: number, public es: Link[], getSourceIndex: (l: Link) => number, getTargetIndex: (l: Link) => number, getLength: (l: Link) => number) {
|
|
this.neighbours = new Array(this.n);
|
|
var i = this.n; while (i--) this.neighbours[i] = new Node(i);
|
|
|
|
i = this.es.length; while (i--) {
|
|
var e = this.es[i];
|
|
var u: number = getSourceIndex(e), v: number = getTargetIndex(e);
|
|
var d = getLength(e);
|
|
this.neighbours[u].neighbours.push(new Neighbour(v, d));
|
|
this.neighbours[v].neighbours.push(new Neighbour(u, d));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* compute shortest paths for graph over n nodes with edges an array of source/target pairs
|
|
* edges may optionally have a length attribute. 1 is the default.
|
|
* Uses Johnson's algorithm.
|
|
*
|
|
* @method DistanceMatrix
|
|
* @return the distance matrix
|
|
*/
|
|
DistanceMatrix(): number[][] {
|
|
var D = new Array(this.n);
|
|
for (var i = 0; i < this.n; ++i) {
|
|
D[i] = this.dijkstraNeighbours(i);
|
|
}
|
|
return D;
|
|
}
|
|
|
|
/**
|
|
* get shortest paths from a specified start node
|
|
* @method DistancesFromNode
|
|
* @param start node index
|
|
* @return array of path lengths
|
|
*/
|
|
DistancesFromNode(start: number): number[] {
|
|
return this.dijkstraNeighbours(start);
|
|
}
|
|
|
|
PathFromNodeToNode(start: number, end: number): number[] {
|
|
return this.dijkstraNeighbours(start, end);
|
|
}
|
|
|
|
// find shortest path from start to end, with the opportunity at
|
|
// each edge traversal to compute a custom cost based on the
|
|
// previous edge. For example, to penalise bends.
|
|
PathFromNodeToNodeWithPrevCost(
|
|
start: number,
|
|
end: number,
|
|
prevCost: (u:number,v:number,w:number)=>number): number[]
|
|
{
|
|
var q = new PriorityQueue<QueueEntry>((a, b) => a.d <= b.d),
|
|
u: Node = this.neighbours[start],
|
|
qu: QueueEntry = new QueueEntry(u,null,0),
|
|
visitedFrom = {};
|
|
q.push(qu);
|
|
while(!q.empty()) {
|
|
qu = q.pop();
|
|
u = qu.node;
|
|
if (u.id === end) {
|
|
break;
|
|
}
|
|
var i = u.neighbours.length; while (i--) {
|
|
var neighbour = u.neighbours[i],
|
|
v = this.neighbours[neighbour.id];
|
|
|
|
// don't double back
|
|
if (qu.prev && v.id === qu.prev.node.id) continue;
|
|
|
|
// don't retraverse an edge if it has already been explored
|
|
// from a lower cost route
|
|
var viduid = v.id + ',' + u.id;
|
|
if(viduid in visitedFrom && visitedFrom[viduid] <= qu.d)
|
|
continue;
|
|
|
|
var cc = qu.prev ? prevCost(qu.prev.node.id, u.id, v.id) : 0,
|
|
t = qu.d + neighbour.distance + cc;
|
|
|
|
// store cost of this traversal
|
|
visitedFrom[viduid] = t;
|
|
q.push(new QueueEntry(v, qu, t));
|
|
}
|
|
}
|
|
var path:number[] = [];
|
|
while (qu.prev) {
|
|
qu = qu.prev;
|
|
path.push(qu.node.id);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
private dijkstraNeighbours(start: number, dest: number = -1): number[] {
|
|
var q = new PriorityQueue<Node>((a, b) => a.d <= b.d),
|
|
i = this.neighbours.length,
|
|
d: number[] = new Array(i);
|
|
while (i--) {
|
|
var node: Node = this.neighbours[i];
|
|
node.d = i === start ? 0 : Number.POSITIVE_INFINITY;
|
|
node.q = q.push(node);
|
|
}
|
|
while (!q.empty()) {
|
|
// console.log(q.toString(function (u) { return u.id + "=" + (u.d === Number.POSITIVE_INFINITY ? "\u221E" : u.d.toFixed(2) )}));
|
|
var u = q.pop();
|
|
d[u.id] = u.d;
|
|
if (u.id === dest) {
|
|
var path: number[] = [];
|
|
var v = u;
|
|
while (typeof v.prev !== 'undefined') {
|
|
path.push(v.prev.id);
|
|
v = v.prev;
|
|
}
|
|
return path;
|
|
}
|
|
i = u.neighbours.length; while (i--) {
|
|
var neighbour = u.neighbours[i];
|
|
var v = this.neighbours[neighbour.id];
|
|
var t = u.d + neighbour.distance;
|
|
if (u.d !== Number.MAX_VALUE && v.d > t) {
|
|
v.d = t;
|
|
v.prev = u;
|
|
q.reduceKey(v.q, v, (e,q)=>e.q = q);
|
|
}
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
}
|