import { PriorityQueue } from '../extra/PriorityQueue.js';
/**
* Implementation of Dijkstra's algorithm.
*
* @author {@link https://github.com/Mugen87|Mugen87}
*/
class Dijkstra {
/**
* Constructs a Dijkstra algorithm object.
*
* @param {Graph} graph - The graph.
* @param {Number} source - The node index of the source node.
* @param {Number} target - The node index of the target node.
*/
constructor( graph = null, source = - 1, target = - 1 ) {
/**
* The graph.
* @type {?Graph}
* @default null
*/
this.graph = graph;
/**
* The node index of the source node.
* @type {Number}
* @default - 1
*/
this.source = source;
/**
* The node index of the target node.
* @type {Number}
* @default - 1
*/
this.target = target;
/**
* Whether the search was successful or not.
* @type {Boolean}
* @default false
*/
this.found = false;
this._cost = new Map(); // total cost of the bast path so far for a given node
this._shortestPathTree = new Map();
this._searchFrontier = new Map();
}
/**
* Executes the graph search. If the search was successful, {@link Dijkstra#found}
* is set to true.
*
* @return {Dijkstra} A reference to this Dijkstra object.
*/
search() {
const outgoingEdges = new Array();
const pQueue = new PriorityQueue( compare );
pQueue.push( {
cost: 0,
index: this.source
} );
// while the queue is not empty
while ( pQueue.length > 0 ) {
const nextNode = pQueue.pop();
const nextNodeIndex = nextNode.index;
// if the shortest path tree has the given node, we already found the shortest
// path to this particular one
if ( this._shortestPathTree.has( nextNodeIndex ) ) continue;
// move this edge from the frontier to the shortest path tree
if ( this._searchFrontier.has( nextNodeIndex ) === true ) {
this._shortestPathTree.set( nextNodeIndex, this._searchFrontier.get( nextNodeIndex ) );
}
// if the target has been found exit
if ( nextNodeIndex === this.target ) {
this.found = true;
return this;
}
// now relax the edges
this.graph.getEdgesOfNode( nextNodeIndex, outgoingEdges );
for ( let i = 0, l = outgoingEdges.length; i < l; i ++ ) {
const edge = outgoingEdges[ i ];
// the total cost to the node this edge points to is the cost to the
// current node plus the cost of the edge connecting them.
const newCost = ( this._cost.get( nextNodeIndex ) || 0 ) + edge.cost;
// We enhance our search frontier in two cases:
// 1. If the node was never on the search frontier
// 2. If the cost to this node is better than before
if ( ( this._searchFrontier.has( edge.to ) === false ) || newCost < ( this._cost.get( edge.to ) ) ) {
this._cost.set( edge.to, newCost );
this._searchFrontier.set( edge.to, edge );
pQueue.push( {
cost: newCost,
index: edge.to
} );
}
}
}
this.found = false;
return this;
}
/**
* Returns the shortest path from the source to the target node as an array of node indices.
*
* @return {Array<Number>} The shortest path.
*/
getPath() {
// array of node indices that comprise the shortest path from the source to the target
const path = new Array();
// just return an empty path if no path to target found or if no target has been specified
if ( this.found === false || this.target === - 1 ) return path;
// start with the target of the path
let currentNode = this.target;
path.push( currentNode );
// while the current node is not the source node keep processing
while ( currentNode !== this.source ) {
// determine the parent of the current node
currentNode = this._shortestPathTree.get( currentNode ).from;
// push the new current node at the beginning of the array
path.unshift( currentNode );
}
return path;
}
/**
* Returns the search tree of the algorithm as an array of edges.
*
* @return {Array<Edge>} The search tree.
*/
getSearchTree() {
return [ ...this._shortestPathTree.values() ];
}
/**
* Clears the internal state of the object. A new search is now possible.
*
* @return {Dijkstra} A reference to this Dijkstra object.
*/
clear() {
this.found = false;
this._cost.clear();
this._shortestPathTree.clear();
this._searchFrontier.clear();
return this;
}
}
function compare( a, b ) {
return ( a.cost < b.cost ) ? - 1 : ( a.cost > b.cost ) ? 1 : 0;
}
export { Dijkstra };