122 lines
5.1 KiB
PHP
122 lines
5.1 KiB
PHP
<?php
|
|
|
|
namespace Graphp\Algorithms\ShortestPath;
|
|
|
|
use Fhaculty\Graph\Set\Edges;
|
|
use Fhaculty\Graph\Exception\UnexpectedValueException;
|
|
use \SplPriorityQueue;
|
|
|
|
/**
|
|
* Commonly used Dijkstra's shortest path algorithm
|
|
*
|
|
* This is asymptotically the fastest known single-source shortest-path
|
|
* algorithm for arbitrary graphs with non-negative weights. If your Graph
|
|
* contains an Edge with negative weight, if will throw an
|
|
* UnexpectedValueException. Consider using (the slower) MooreBellmanFord
|
|
* algorithm instead.
|
|
*
|
|
* @link http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
|
|
* @see MooreBellmanFord
|
|
*/
|
|
class Dijkstra extends Base3
|
|
{
|
|
/**
|
|
* get all edges on shortest path for this vertex
|
|
*
|
|
* @return Edges
|
|
* @throws UnexpectedValueException when encountering an Edge with negative weight
|
|
*/
|
|
public function getEdges()
|
|
{
|
|
$totalCostOfCheapestPathTo = Array();
|
|
// start node distance
|
|
$totalCostOfCheapestPathTo[$this->vertex->getId()] = INF;
|
|
|
|
// just to get the cheapest vertex in the correct order
|
|
$cheapestVertex = new SplPriorityQueue();
|
|
$cheapestVertex->insert($this->vertex, 0);
|
|
|
|
// predecessor
|
|
$predecesVertexOfCheapestPathTo = Array();
|
|
$predecesVertexOfCheapestPathTo[$this->vertex->getId()] = $this->vertex;
|
|
|
|
// mark vertices when their cheapest path has been found
|
|
$usedVertices = Array();
|
|
|
|
$isFirst = true;
|
|
|
|
// Repeat until all vertices have been marked
|
|
$totalCountOfVertices = count($this->vertex->getGraph()->getVertices());
|
|
for ($i = 0; $i < $totalCountOfVertices; ++$i) {
|
|
$currentVertex = NULL;
|
|
$currentVertexId = NULL;
|
|
$isEmpty = false;
|
|
do {
|
|
// if the priority queue is empty there are isolated vertices, but the algorithm visited all other vertices
|
|
if ($cheapestVertex->isEmpty()) {
|
|
$isEmpty = true;
|
|
break;
|
|
}
|
|
// Get cheapest unmarked vertex
|
|
$currentVertex = $cheapestVertex->extract();
|
|
$currentVertexId = $currentVertex->getId();
|
|
// Vertices can be in the priority queue multiple times, with different path costs (if vertex is already marked, this is an old unvalid entry)
|
|
} while (isset($usedVertices[$currentVertexId]));
|
|
|
|
// catch "algorithm ends" condition
|
|
if ($isEmpty) {
|
|
break;
|
|
}
|
|
|
|
if ($isFirst) {
|
|
$isFirst = false;
|
|
} else {
|
|
// mark this vertex
|
|
$usedVertices[$currentVertexId] = true;
|
|
}
|
|
|
|
// check for all edges of current vertex if there is a cheaper path (or IN OTHER WORDS: Add reachable nodes from currently added node and refresh the current possible distances)
|
|
foreach ($currentVertex->getEdgesOut() as $edge) {
|
|
$weight = $edge->getWeight();
|
|
if ($weight < 0) {
|
|
throw new UnexpectedValueException('Djkstra not supported for negative weights - Consider using MooreBellmanFord');
|
|
}
|
|
|
|
$targetVertex = $edge->getVertexToFrom($currentVertex);
|
|
$targetVertexId = $targetVertex->getId();
|
|
|
|
// if the targetVertex is marked, the cheapest path for this vertex has already been found (no negative edges) {
|
|
if (!isset($usedVertices[$targetVertexId])) {
|
|
// calculate new cost to vertex
|
|
$newCostsToTargetVertex = $totalCostOfCheapestPathTo[$currentVertexId] + $weight;
|
|
if (is_infinite($newCostsToTargetVertex)) {
|
|
$newCostsToTargetVertex = $weight;
|
|
}
|
|
|
|
if ((!isset($predecesVertexOfCheapestPathTo[$targetVertexId]))
|
|
// is the new path cheaper?
|
|
|| $totalCostOfCheapestPathTo[$targetVertexId] > $newCostsToTargetVertex){
|
|
|
|
// Not an update, just an new insert with lower cost
|
|
$cheapestVertex->insert($targetVertex, - $newCostsToTargetVertex);
|
|
// so the lowest cost will be extraced first
|
|
// and higher cost will be skipped during extraction
|
|
|
|
// update/set costs found with the new connection
|
|
$totalCostOfCheapestPathTo[$targetVertexId] = $newCostsToTargetVertex;
|
|
// update/set predecessor vertex from the new connection
|
|
$predecesVertexOfCheapestPathTo[$targetVertexId] = $currentVertex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($totalCostOfCheapestPathTo[$this->vertex->getId()] === INF) {
|
|
unset($predecesVertexOfCheapestPathTo[$this->vertex->getId()]);
|
|
}
|
|
|
|
// algorithm is done, return resulting edges
|
|
return $this->getEdgesCheapestPredecesor($predecesVertexOfCheapestPathTo);
|
|
}
|
|
}
|