programming-examples/php/Algo/MooreBellmanFord.php
2019-11-15 12:59:38 +01:00

124 lines
4.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 "BellmanFord 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');
}
}