You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
4.5 KiB
PHTML

5 years ago
<?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');
}
}