src/Algo/DijkstraMulti.php
<?php
/**
* Multi-path version of Dijkstra's shortest path algorithm.
*/
namespace GraphDS\Algo;
use GraphDS\Graph\Graph;
use GraphDS\Graph\DirectedGraph;
use GraphDS\Graph\UndirectedGraph;
use InvalidArgumentException;
/**
* Class defining the multi-path version of Dijkstra's shortest path algorithm.
*/
class DijkstraMulti
{
/**
* Reference to the graph.
*
* @var Graph
*/
public $graph;
/**
* Array holding the shortest distances to each vertex from the source.
*
* @var array
*/
public $dist = array();
/**
* Array holding the previous vertices for each vertex for the shortest path.
*
* @var array
*/
public $prev = array();
/**
* Array holding the unvisited vertices in the graph.
*
* @var array
*/
public $unvisitedVertices = array();
/**
* ID of the start vertex.
*
* @var mixed
*/
public $start;
/**
* Array holding a single shortest path.
*
* @var array
*/
public $path = array();
/**
* Array holding the shortest paths in the graph.
*
* @var array
*/
public $paths = array();
/**
* Constructor for the multi-path version of Dijkstra algorithm.
*
* @param Graph $graph The graph to which the multi-path Dijkstra algorithm should be applied
*/
public function __construct(Graph $graph)
{
if (!($graph instanceof DirectedGraph) && !($graph instanceof UndirectedGraph)) {
throw new InvalidArgumentException(
"Dijkstra's shortest path algorithm requires a directed or undirected graph"
);
}
$this->graph = &$graph;
}
/**
* Calculates the shortest path to every vertex from vertex $start.
*
* @param mixed $start ID of the starting vertex for multi-path Dijkstra's algorithm
*
* @return array Array holding the distances and previous vertices as calculated by Dijkstra's algorithm
*
* @throws InvalidArgumentException
*/
public function run($start)
{
$this->start = $start;
if (empty($this->graph->vertices[$start])) {
throw new InvalidArgumentException("Vertex $start does not exist.");
}
foreach (array_keys($this->graph->vertices) as $vertex) {
$this->dist[$vertex] = INF;
$this->prev[$vertex] = null;
$this->unvisitedVertices[$vertex] = null;
}
$this->dist[$start] = 0;
while (count($this->unvisitedVertices) > 0) {
$distUnvisited = array_intersect_key($this->dist, $this->unvisitedVertices);
$minVertexTmp = array_keys($distUnvisited, min($distUnvisited));
$minVertex = $minVertexTmp[0];
unset($this->unvisitedVertices[$minVertex]);
if ($this->graph instanceof UndirectedGraph) {
$neighbors = $this->graph->vertices[$minVertex]->getNeighbors();
} elseif ($this->graph instanceof DirectedGraph) {
$neighbors = $this->graph->vertices[$minVertex]->getOutNeighbors();
}
foreach ($neighbors as $vertex) {
$alt = $this->dist[$minVertex] + $this->graph->edge($minVertex, $vertex)->getValue();
if ($alt < $this->dist[$vertex]) {
$this->dist[$vertex] = $alt;
$this->prev[$vertex] = null;
$this->prev[$vertex][] = $minVertex;
} elseif ($alt === $this->dist[$vertex]) {
$this->prev[$vertex][] = $minVertex;
}
}
}
return $this->prev;
}
/**
* Returns all shortest paths to $dest from the origin vertex $this->start in the graph.
*
* @param string $dest ID of the destination vertex
*
* @return array An array containing the shortest path and distance
*/
public function get($dest)
{
$this->paths = array();
$this->enumerate($dest, $this->start);
return array(
'paths' => $this->paths,
'dist' => $this->dist[$dest],
);
}
/**
* Enumerates the result of the multi-path Dijkstra as paths.
*
* @param mixed $source ID of the source vertex
* @param mixed $dest ID of the destination vertex
*/
private function enumerate($source, $dest)
{
array_unshift($this->path, $source);
$discovered[] = $source;
if ($source === $dest) {
$this->paths[] = $this->path;
} else {
if (!$this->prev[$source]) {
return;
}
foreach ($this->prev[$source] as $child) {
if (!in_array($child, $discovered)) {
$this->enumerate($child, $dest);
}
}
}
array_shift($this->path);
if (($key = array_search($source, $discovered)) !== false) {
unset($discovered[$key]);
}
}
}