BreadthFirstPaths::bfs()   A
last analyzed

Complexity

Conditions 6
Paths 9

Size

Total Lines 39
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 9
nop 0
dl 0
loc 39
rs 9.0444
c 0
b 0
f 0
1
<?php
2
3
namespace TemplesOfCode\NodesAndEdges\BFS;
4
5
use TemplesOfCode\NodesAndEdges\Graph;
6
7
/**
8
 * Class BreadthFirstPaths
9
 * @package TemplesOfCode\NodesAndEdges\BFS
10
 */
11
class BreadthFirstPaths
12
{
13
    /**
14
     * @var int
15
     */
16
    private const INFINITY = PHP_INT_MAX;
17
18
    /**
19
     * @var bool[]
20
     */
21
    private $marked;
22
23
    /**
24
     * edgeTo[v] = previous edge on shortest s-v path
25
     *
26
     * @var int[]
27
     */
28
    private $edgeTo;
29
30
    /**
31
     * distTo[v] = number of edges shortest s-v path
32
     *
33
     * @var int[]
34
     */
35
    private $distTo;
36
37
    /**
38
     * @var mixed
39
     */
40
    private $sourceV;
41
42
    /**
43
     * @var Graph
44
     */
45
    private $graph;
46
47
    /**
48
     * @param Graph $graph
49
     * @param mixed $sourceV
50
     */
51
    public function __construct(Graph $graph, $sourceV)
52
    {
53
        // set
54
        $vertices = $graph->getVertices();
55
        // init
56
        $this->distTo = array_fill(0, $vertices, static::INFINITY);
57
        // check for data type
58
        if (is_array($sourceV)) {
59
            // handle multiple source vertices
60
            $sourceVertices = $sourceV;
61
            // iterate over the set of vertices
62
            foreach ($sourceVertices as $vertex) {
63
                // validate this vertex in the context of the given graph
64
                Graph::validateVertex($vertex, $vertices);
65
            }
66
        } else {
67
            // handle single vertex
68
            $sourceVertex = $sourceV;
69
            // validate this vertex in the context of the given graph
70
            Graph::validateVertex($sourceVertex, $vertices);
71
            // the distance to our source vertex is always zero
72
            $this->distTo[$sourceVertex] = 0;
73
        }
74
        // set
75
        $this->sourceV = $sourceV;
76
        // init
77
        $this->marked = array_fill(0, $vertices, false);
78
        // init
79
        $this->edgeTo = [];
80
        // set
81
        $this->graph = $graph;
82
        // invoke bfs
83
        $this->bfs();
84
        // check for data type
85
        if (!is_array($sourceV)) {
86
            // assert correct build
87
            assert($this->check($this->graph, $sourceV));
88
        }
89
    }
90
91
    /**
92
     *
93
     */
94
    private function bfs()
95
    {
96
        // init
97
        $queue = [];
98
        // check for data type
99
        if (is_array($this->sourceV)) {
100
            // iterate over the set of source vertices
101
            foreach ($this->sourceV as $vertex) {
102
                // consider this vertex visited
103
                $this->marked[$vertex] = true;
104
                // set the distance
105
                $this->distTo[$vertex] = 0;
106
                // add to the queue
107
                $queue[] = $vertex;
108
            }
109
        } else {
110
            // init
111
            $this->marked[$this->sourceV] = true;
112
            // add beginning of queue
113
            $queue = [$this->sourceV];
114
        }
115
        // begin loop over the queue
116
        while (!empty($queue)) {
117
            // get the next vertex
118
            $vertex = array_shift($queue);
119
            // get the neighbors
120
            $neighbors = $this->graph->adjacent($vertex);
121
            // iterate over the adjacent vertices
122
            foreach ($neighbors as $w) {
123
                // process only if this vertex has been visited
124
                if (!$this->marked[$w]) {
125
                    // $w can be reached via $vertex
126
                    $this->edgeTo[$w] = $vertex;
127
                    // add to the distance
128
                    $this->distTo[$w] = $this->distTo[$vertex] + 1;
129
                    // consider this vertex visited
130
                    $this->marked[$w] = true;
131
                    // add to the queue
132
                    $queue[] = $w;
133
                }
134
            }
135
        }
136
    }
137
138
    /**
139
     * @param int $vertex
140
     * @return bool
141
     */
142
    public function hasPathTo(int $vertex)
143
    {
144
         // validate this vertex in the context of the given graph
145
        Graph::validateVertex($vertex, $this->graph->getVertices());
146
        // return the flag
147
        return $this->marked[$vertex];
148
    }
149
150
    /**
151
     * @param int $vertex
152
     * @return int
153
     */
154
    public function distTo(int $vertex)
155
    {
156
         // validate this vertex in the context of the given graph
157
        Graph::validateVertex($vertex, $this->graph->getVertices());
158
        // return the value
159
        return $this->distTo[$vertex];
160
    }
161
162
    /**
163
     * @param int $vertex
164
     * @return array|null
165
     */
166
    public function pathTo(int $vertex)
167
    {
168
         // validate this vertex in the context of the given graph
169
        Graph::validateVertex($vertex, $this->graph->getVertices());
170
        // check if there is a path
171
        if (!$this->hasPathTo($vertex)) {
172
            // there is no path
173
            return null;
174
        }
175
        // init
176
        $path = [];
177
        // iterate over the path
178
        for ($x = $vertex; $this->distTo[$x] != 0; $x = $this->edgeTo[$x]) {
179
            // add this vertex 
180
            array_unshift($path, $x);
181
        }
182
        // add final vertex
183
        array_unshift($path, $x);
184
        // return the path
185
        return $path;
186
    }
187
188
    /**
189
     * @param Graph $graph
190
     * @param int $sourceVertex
191
     * @return bool
192
     */
193
    private function check(Graph $graph, int $sourceVertex)
194
    {
195
        // check that the distance of s = 0
196
        if ($this->distTo[$sourceVertex] != 0) {
197
            // check failed
198
            return false;
199
        }
200
        // init
201
        $vertices = $graph->getVertices();
202
        // check that for each edge v-w dist[w] <= dist[v] + 1
203
        // provided v is reachable from s
204
        for ($vertex = 0; $vertex < $vertices; $vertex++) {
205
            // get neighbors
206
            $neighbors = $graph->adjacent($vertex);
207
            // iterate over the neighbors
208
            foreach ($neighbors as $w) {
209
                // check paths
210
                if ($this->hasPathTo($vertex) != $this->hasPathTo($w)) {
211
                    // check failed
212
                    return false;
213
                }
214
                // check distances
215
                if ($this->hasPathTo($vertex) && ($this->distTo[$w] > $this->distTo[$vertex] + 1)) {
216
                    // check failed
217
                    return false;
218
                }
219
            }
220
        }
221
        // check that v = edgeTo[w] satisfies distTo[w] = distTo[v] + 1
222
        // provided v is reachable from s
223
        for ($w = 0; $w < $vertices; $w++) {
224
            if (!$this->hasPathTo($w) || $w == $sourceVertex) {
225
                // good to go
226
                continue;
227
            }
228
            // set
229
            $v = $this->edgeTo[$w];
230
            // check distances
231
            if ($this->distTo[$w] != $this->distTo[$v] + 1) {
232
                // check failed
233
                return false;
234
            }
235
        }
236
        // check passed
237
        return true;
238
    }
239
}
240