bingo-soft /
graphp
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Graphp\Alg\Shortestpath; |
||
| 4 | |||
| 5 | use Exception; |
||
| 6 | use InvalidArgumentException; |
||
| 7 | use Heap\AddressableHeapInterface; |
||
| 8 | use Heap\Tree\PairingHeap; |
||
| 9 | use Graphp\GraphInterface; |
||
| 10 | use Graphp\GraphUtils; |
||
| 11 | use Graphp\Edge\EdgeInterface; |
||
| 12 | use Graphp\Vertex\VertexInterface; |
||
| 13 | use Graphp\Util\Supplier; |
||
| 14 | use Graphp\Alg\Interfaces\SingleSourcePathsInterface; |
||
| 15 | |||
| 16 | /** |
||
| 17 | * Class DijkstraClosestFirstIterator |
||
| 18 | * |
||
| 19 | * @package Graphp\Alg\Shortestpath; |
||
| 20 | */ |
||
| 21 | class DijkstraClosestFirstIterator |
||
| 22 | { |
||
| 23 | /** |
||
| 24 | * The graph |
||
| 25 | * |
||
| 26 | * @var GraphInterface |
||
| 27 | */ |
||
| 28 | private $graph; |
||
| 29 | |||
| 30 | /** |
||
| 31 | * The source vertex |
||
| 32 | * |
||
| 33 | * @var VertexInterface |
||
| 34 | */ |
||
| 35 | private $source; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * The radius |
||
| 39 | * |
||
| 40 | * @var float |
||
| 41 | */ |
||
| 42 | private $radius; |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Already seen vertices |
||
| 46 | * |
||
| 47 | * @var array |
||
| 48 | */ |
||
| 49 | private $seen = []; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * The underlying heap implementation |
||
| 53 | * |
||
| 54 | * @var AddressableHeapInterface |
||
| 55 | */ |
||
| 56 | private $heap; |
||
| 57 | |||
| 58 | /** |
||
| 59 | * Create a new iterator for the specified graph. |
||
| 60 | * If radius is specified, iteration will start at the source vertex and |
||
| 61 | * will be limited to the subset of paths of weighted length less than or equal to the radius |
||
| 62 | * |
||
| 63 | * @param GraphInterface $graph - the graph |
||
| 64 | * @param VertexInterface $source - the source vertex |
||
| 65 | * @param float $radius - the limit on weighted path length |
||
| 66 | * @param Supplier $heapSupplier - supplier of the preferable heap implementation |
||
| 67 | * |
||
| 68 | * @throws InvalidArgumentException |
||
| 69 | */ |
||
| 70 | 6 | public function __construct( |
|
| 71 | GraphInterface $graph, |
||
| 72 | VertexInterface $source, |
||
| 73 | ?float $radius = null, |
||
| 74 | ?Supplier $heapSupplier = null |
||
| 75 | ) { |
||
| 76 | 6 | $this->graph = $graph; |
|
| 77 | 6 | $this->source = $source; |
|
| 78 | 6 | $this->radius = $radius ?? INF; |
|
| 79 | 6 | if ($this->radius < 0.0) { |
|
| 80 | throw new InvalidArgumentException("Radius must be non-negative"); |
||
| 81 | } |
||
| 82 | 6 | $this->seen = []; |
|
| 83 | 6 | $this->heap = is_null($heapSupplier) ? new PairingHeap() : $heapSupplier->get(); |
|
| 84 | 6 | $this->updateDistance($this->source, null, 0.0); |
|
| 85 | 6 | } |
|
| 86 | |||
| 87 | /** |
||
| 88 | * Checks if iterator has next element to look up |
||
| 89 | * |
||
| 90 | * @return bool |
||
| 91 | */ |
||
| 92 | 6 | public function hasNext(): bool |
|
| 93 | { |
||
| 94 | 6 | if ($this->heap->isEmpty()) { |
|
| 95 | 3 | return false; |
|
| 96 | } |
||
| 97 | 6 | $vNode = $this->heap->findMin(); |
|
| 98 | 6 | $vDistance = $vNode->getKey(); |
|
| 99 | 6 | if ($this->radius < $vDistance) { |
|
| 100 | 2 | $this->heap->clear(); |
|
| 101 | 2 | return false; |
|
| 102 | } |
||
| 103 | 6 | return true; |
|
| 104 | } |
||
| 105 | |||
| 106 | /** |
||
| 107 | * Get the next vertex |
||
| 108 | * |
||
| 109 | * @return VertexInterface |
||
| 110 | */ |
||
| 111 | 6 | public function next(): VertexInterface |
|
| 112 | { |
||
| 113 | 6 | if (!$this->hasNext()) { |
|
| 114 | throw new Exception("No such element!"); |
||
| 115 | } |
||
| 116 | |||
| 117 | // settle next node |
||
| 118 | 6 | $vNode = $this->heap->deleteMin(); |
|
| 119 | 6 | $v = $vNode->getValue()[0]; |
|
| 120 | 6 | $vDistance = $vNode->getKey(); |
|
| 121 | |||
| 122 | // relax edges |
||
| 123 | 6 | $edges = $this->graph->outgoingEdgesOf($v); |
|
| 124 | 6 | foreach ($edges as $edge) { |
|
| 125 | 6 | $u = GraphUtils::getOppositeVertex($this->graph, $edge, $v); |
|
| 126 | 6 | $eWeight = $this->graph->getEdgeWeight($edge); |
|
| 127 | 6 | if ($eWeight < 0.0) { |
|
| 128 | 1 | throw new InvalidArgumentException("Negative edge weight not allowed"); |
|
| 129 | } |
||
| 130 | 5 | $this->updateDistance($u, $edge, $vDistance + $eWeight); |
|
| 131 | } |
||
| 132 | |||
| 133 | 5 | return $v; |
|
| 134 | } |
||
| 135 | |||
| 136 | /** |
||
| 137 | * Get the paths computed by the iterator |
||
| 138 | * |
||
| 139 | * @return SingleSourcePathsInterface |
||
| 140 | */ |
||
| 141 | 5 | public function getPaths(): SingleSourcePathsInterface |
|
| 142 | { |
||
| 143 | 5 | return new TreeSingleSourcePaths($this->graph, $this->source, $this->getDistanceAndPredecessorMap()); |
|
| 144 | } |
||
| 145 | |||
| 146 | /** |
||
| 147 | * Get all paths using the traditional representation of the shortest path tree, which stores |
||
| 148 | * for each vertex (a) the distance of the path from the source vertex and (b) the last edge |
||
| 149 | * used to reach the vertex from the source vertex |
||
| 150 | * |
||
| 151 | * @return array |
||
| 152 | */ |
||
| 153 | 5 | public function getDistanceAndPredecessorMap(): array |
|
| 154 | { |
||
| 155 | 5 | $distanceAndPredecessorMap = []; |
|
| 156 | |||
| 157 | 5 | foreach ($this->seen as $vertexHash => $vNode) { |
|
| 158 | 5 | $vDistance = $vNode->getKey(); |
|
| 159 | 5 | if ($this->radius < $vDistance) { |
|
| 160 | 2 | continue; |
|
| 161 | } |
||
| 162 | 5 | $v = $vNode->getValue()[0]; |
|
|
0 ignored issues
–
show
Unused Code
introduced
by
Loading history...
|
|||
| 163 | 5 | $distanceAndPredecessorMap[$vertexHash] = [$vDistance, $vNode->getValue()[1]]; |
|
| 164 | } |
||
| 165 | |||
| 166 | 5 | return $distanceAndPredecessorMap; |
|
| 167 | } |
||
| 168 | |||
| 169 | /** |
||
| 170 | * Update the distance to the vertex |
||
| 171 | * |
||
| 172 | * @param VertexInterface $v - the vertex |
||
| 173 | * @param EdgeInterface $e - the predecessor edge |
||
| 174 | * @param float $distance - the new distance |
||
| 175 | */ |
||
| 176 | 6 | private function updateDistance(VertexInterface $v, ?EdgeInterface $e = null, float $distance): void |
|
| 177 | { |
||
| 178 | 6 | $id = $v->getHash(); |
|
| 179 | 6 | if (!array_key_exists($id, $this->seen)) { |
|
| 180 | 6 | $node = $this->heap->insert($distance, [$v, $e]); |
|
| 181 | 6 | $this->seen[$id] = $node; |
|
| 182 | } else { |
||
| 183 | 5 | $node = $this->seen[$id]; |
|
| 184 | 5 | if ($distance < $node->getKey()) { |
|
| 185 | 5 | $node->decreaseKey($distance); |
|
| 186 | 5 | $node->setValue([$node->getValue()[0], $e]); |
|
| 187 | } |
||
| 188 | } |
||
| 189 | 6 | } |
|
| 190 | } |
||
| 191 |