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
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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