124 lines
4.5 KiB
PHP
124 lines
4.5 KiB
PHP
<?php
|
||
|
||
namespace Graphp\Algorithms\ShortestPath;
|
||
|
||
use Fhaculty\Graph\Edge\Base as Edge;
|
||
use Fhaculty\Graph\Set\Edges;
|
||
use Fhaculty\Graph\Walk;
|
||
use Fhaculty\Graph\Exception\NegativeCycleException;
|
||
use Fhaculty\Graph\Exception\UnderflowException;
|
||
|
||
/**
|
||
* Moore-Bellman-Ford's shortest path algorithm
|
||
*
|
||
* It is slower than Dijkstra's algorithm for the same problem, but more
|
||
* versatile, as it is capable of handling Graphs with negative Edge weights.
|
||
*
|
||
* Also known as just "Bellman–Ford algorithm".
|
||
*
|
||
* @link http://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
|
||
*/
|
||
class MooreBellmanFord extends Base3
|
||
{
|
||
/**
|
||
*
|
||
*
|
||
* @param Edges $edges
|
||
* @param int[] $totalCostOfCheapestPathTo
|
||
* @param Vertex[] $predecessorVertexOfCheapestPathTo
|
||
*
|
||
* @return Vertex|NULL
|
||
*/
|
||
private function bigStep(Edges $edges, array &$totalCostOfCheapestPathTo, array &$predecessorVertexOfCheapestPathTo)
|
||
{
|
||
$changed = NULL;
|
||
// check for all edges
|
||
foreach ($edges as $edge) {
|
||
// check for all "ends" of this edge (or for all targetes)
|
||
foreach ($edge->getVerticesTarget() as $toVertex) {
|
||
$fromVertex = $edge->getVertexFromTo($toVertex);
|
||
|
||
// If the fromVertex already has a path
|
||
if (isset($totalCostOfCheapestPathTo[$fromVertex->getId()])) {
|
||
// New possible costs of this path
|
||
$newCost = $totalCostOfCheapestPathTo[$fromVertex->getId()] + $edge->getWeight();
|
||
if (is_infinite($newCost)) {
|
||
$newCost = $edge->getWeight() + 0;
|
||
}
|
||
|
||
// No path has been found yet
|
||
if (!isset($totalCostOfCheapestPathTo[$toVertex->getId()])
|
||
// OR this path is cheaper than the old path
|
||
|| $totalCostOfCheapestPathTo[$toVertex->getId()] > $newCost){
|
||
|
||
$changed = $toVertex;
|
||
$totalCostOfCheapestPathTo[$toVertex->getId()] = $newCost;
|
||
$predecessorVertexOfCheapestPathTo[$toVertex->getId()] = $fromVertex;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return $changed;
|
||
}
|
||
|
||
/**
|
||
* Calculate the Moore-Bellman-Ford-Algorithm and get all edges on shortest path for this vertex
|
||
*
|
||
* @return Edges
|
||
* @throws NegativeCycleException if there is a negative cycle
|
||
*/
|
||
public function getEdges()
|
||
{
|
||
// start node distance, add placeholder weight
|
||
$totalCostOfCheapestPathTo = array($this->vertex->getId() => INF);
|
||
|
||
// predecessor
|
||
$predecessorVertexOfCheapestPathTo = array($this->vertex->getId() => $this->vertex);
|
||
|
||
// the usal algorithm says we repeat (n-1) times.
|
||
// but because we also want to check for loop edges on the start vertex,
|
||
// we have to add an additional step:
|
||
$numSteps = count($this->vertex->getGraph()->getVertices());
|
||
$edges = $this->vertex->getGraph()->getEdges();
|
||
$changed = true;
|
||
|
||
for ($i = 0; $i < $numSteps && $changed; ++$i) {
|
||
$changed = $this->bigStep($edges, $totalCostOfCheapestPathTo, $predecessorVertexOfCheapestPathTo);
|
||
}
|
||
|
||
// no cheaper edge to start vertex found => remove placeholder weight
|
||
if ($totalCostOfCheapestPathTo[$this->vertex->getId()] === INF) {
|
||
unset($predecessorVertexOfCheapestPathTo[$this->vertex->getId()]);
|
||
}
|
||
|
||
// algorithm is done, build graph
|
||
$returnEdges = $this->getEdgesCheapestPredecesor($predecessorVertexOfCheapestPathTo);
|
||
|
||
// Check for negative cycles (only if last step didn't already finish anyway)
|
||
// something is still changing...
|
||
if ($changed && $changed = $this->bigStep($edges, $totalCostOfCheapestPathTo, $predecessorVertexOfCheapestPathTo)) {
|
||
$cycle = Walk::factoryCycleFromPredecessorMap($predecessorVertexOfCheapestPathTo, $changed, Edges::ORDER_WEIGHT);
|
||
throw new NegativeCycleException('Negative cycle found', 0, NULL, $cycle);
|
||
}
|
||
|
||
return $returnEdges;
|
||
}
|
||
|
||
/**
|
||
* get negative cycle
|
||
*
|
||
* @return Walk
|
||
* @throws UnderflowException if there's no negative cycle
|
||
*/
|
||
public function getCycleNegative()
|
||
{
|
||
try {
|
||
$this->getEdges();
|
||
} catch (NegativeCycleException $e) {
|
||
return $e->getCycle();
|
||
}
|
||
throw new UnderflowException('No cycle found');
|
||
}
|
||
}
|