Skip to content

Commit aaca6a5

Browse files
committed
Replace recursive topological sort with iterative algorithm
1 parent 81db404 commit aaca6a5

File tree

1 file changed

+37
-28
lines changed

1 file changed

+37
-28
lines changed

src/TopologicalSort.php

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,47 +19,56 @@ class TopologicalSort extends BaseGraph
1919
/**
2020
* run algorithm and return an ordered/sorted set of Vertices
2121
*
22-
* the topologic sorting may be non-unique depending on your edges. this
23-
* algorithm tries to keep the order of vertices as added to the graph in
24-
* this case.
22+
* the topological sorting may be non-unique depending on your edges
2523
*
2624
* @return Vertices
2725
*/
2826
public function getVertices()
2927
{
30-
$tsl = array();
28+
$stack = array(); // visited nodes with unvisited children
3129
$visited = array();
30+
$output = array();
3231

33-
// TODO: find alternative to recursive algorithm to avoid hitting recursion limit with ~100 nodes
32+
/** @var Vertex $top */
3433
// TODO: avoid having to reverse all vertices multiple times
34+
// pick a node to examine next - assume there are isolated nodes
35+
foreach (array_reverse($this->graph->getVertices()->getVector()) as $top) {
36+
$tid = $top->getId();
37+
if (!isset($visited[$tid])) { // don't examine if already found
38+
array_push($stack, $top);
39+
}
3540

36-
foreach(array_reverse($this->graph->getVertices()->getVector()) as $vertex) {
37-
$this->visit($vertex, $visited, $tsl);
38-
}
41+
while ($stack) {
42+
/** @var Vertex $current */
43+
$node = end($stack);
44+
$nid = $node->getId();
3945

40-
return new Vertices(array_reverse($tsl, true));
41-
}
46+
$visited[$nid] = false; // temporary mark
47+
$found = false; // new children found during visit to this node
4248

43-
protected function visit(Vertex $vertex, array &$visited, array &$tsl)
44-
{
45-
$vid = $vertex->getId();
46-
if (isset($visited[$vid])) {
47-
if ($visited[$vid] === false) {
48-
// temporary mark => not a DAG
49-
throw new UnexpectedValueException('Not a DAG');
50-
}
51-
// otherwise already marked/visisted => no need to check again
52-
} else {
53-
// temporary mark
54-
$visited[$vid] = false;
49+
// find the next node to visit
50+
/** @var Vertex $child */
51+
foreach (array_reverse($node->getVerticesEdgeTo()->getVector()) as $child) {
52+
$cid = $child->getId();
53+
if (!isset($visited[$cid])) {
54+
// found a new node - push it onto the stack
55+
array_push($stack, $child);
56+
$found = true; // move onto the new node
57+
break;
58+
} else if ($visited[$cid] === false) {
59+
// temporary mark => not a DAG
60+
throw new UnexpectedValueException('Not a DAG');
61+
}
62+
}
5563

56-
foreach (array_reverse($vertex->getVerticesEdgeTo()->getVector()) as $v) {
57-
$this->visit($v, $visited, $tsl);
64+
if (!$found) {
65+
array_pop($stack); // no new children found - we're done with this node
66+
$visited[$nid] = true; // mark as visited
67+
array_push($output, $node); // add to results
68+
}
5869
}
59-
60-
// mark as visited and include in result
61-
$visited[$vid] = true;
62-
$tsl[$vid] = $vertex;
6370
}
71+
72+
return new Vertices(array_reverse($output, true));
6473
}
6574
}

0 commit comments

Comments
 (0)