@@ -29,7 +29,6 @@ public TopsortResult<T> topSort(List<T> nodes) {
2929 return new TopsortResult <>(false , result );
3030 }
3131
32-
3332 private @ Nullable TopsortResult <T > topSort_add (List <T > result , Set <T > seen , List <T > seenStack , T n ) {
3433 for (int i = seenStack .size () - 1 ; i >= 0 ; i --) {
3534 if (seenStack .get (i ) == n ) {
@@ -52,7 +51,6 @@ public TopsortResult<T> topSort(List<T> nodes) {
5251 return null ;
5352 }
5453
55-
5654 public static class TopsortResult <T > {
5755 private final boolean isCycle ;
5856 private final List <T > result ;
@@ -69,80 +67,86 @@ public boolean isCycle() {
6967 public List <T > getResult () {
7068 return result ;
7169 }
72-
73-
7470 }
7571
76-
7772 /**
78- * Like topsort, but will find bigger cycles
73+ * Like topsort, but will find bigger cycles.
74+ * This is an iterative implementation of the path-based strong component algorithm
75+ * to prevent StackOverflowErrors on deep graphs.
7976 * <p>
8077 * See https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm
8178 */
8279 public Set <Set <T >> findStronglyConnectedComponents (List <T > nodes ) {
83- // Stack S contains all the vertices that have not yet been assigned to a strongly connected component, in the order in which the depth-first search reaches the vertices.
8480 Deque <T > s = new ArrayDeque <>();
85- // Stack P contains vertices that have not yet been determined to belong to different strongly connected components from each other
8681 Deque <T > p = new ArrayDeque <>();
87- // It also uses a counter C of the number of vertices reached so far, which it uses to compute the preorder numbers of the vertices.
8882 AtomicInteger c = new AtomicInteger ();
8983 AtomicInteger componentCount = new AtomicInteger ();
90- Map <T , Integer > preorderNumber = new LinkedHashMap <>();
91- Map <T , Integer > component = new LinkedHashMap <>();
92-
93- for (T v : nodes ) {
94- if (!preorderNumber .containsKey (v )) {
95- findStronglyConnectedComponentsRec (v , s , p , c , preorderNumber , component , componentCount );
96- }
97- }
98- return ImmutableSet .copyOf (Utils .inverseMapToSet (component ).values ());
99- }
100-
101-
102- private void findStronglyConnectedComponentsRec (T v , Deque <T > s , Deque <T > p , AtomicInteger c , Map <T , Integer > preorderNumber , Map <T , Integer > component , AtomicInteger componentCount ) {
103-
84+ Map <T , Integer > preorderNumber = new HashMap <>();
85+ Map <T , Integer > component = new HashMap <>();
86+
87+ // This stack simulates the recursive calls
88+ Deque <T > traversalStack = new ArrayDeque <>();
89+ // This map holds iterators for the children of each node
90+ Map <T , Iterator <T >> iterators = new HashMap <>();
91+
92+ for (T startNode : nodes ) {
93+ if (!preorderNumber .containsKey (startNode )) {
94+ traversalStack .push (startNode );
95+
96+ while (!traversalStack .isEmpty ()) {
97+ T v = traversalStack .peek ();
98+
99+ // Pre-order processing (first time visiting node v)
100+ if (!preorderNumber .containsKey (v )) {
101+ preorderNumber .put (v , c .getAndIncrement ());
102+ s .push (v );
103+ p .push (v );
104+ iterators .put (v , getIncidentNodes (v ).iterator ());
105+ }
104106
105- // When the depth-first search reaches a vertex v, the algorithm performs the following steps:
106- // 1. Set the preorder number of v to C, and increment C.
107- preorderNumber .put (v , c .getAndIncrement ());
107+ boolean foundNewChild = false ;
108+ Iterator <T > children = iterators .get (v );
109+
110+ // Iterate over children to find the next one to visit
111+ while (children .hasNext ()) {
112+ T w = children .next ();
113+ if (!preorderNumber .containsKey (w )) {
114+ // Found an unvisited child, push to stack to simulate recursive call
115+ traversalStack .push (w );
116+ foundNewChild = true ;
117+ break ;
118+ } else if (!component .containsKey (w )) {
119+ // Child w has been visited but is not yet in a component
120+ while (!p .isEmpty () && preorderNumber .getOrDefault (p .peek (), -1 ) > preorderNumber .get (w )) {
121+ p .pop ();
122+ }
123+ }
124+ }
108125
109- // 2. Push v onto S and also onto P.
110- s .push (v );
111- p .push (v );
126+ if (foundNewChild ) {
127+ // Continue the loop to process the new child on top of the stack
128+ continue ;
129+ }
112130
113- // 3. For each edge from v to a neighboring vertex w:
114- for (T w : getIncidentNodes (v )) {
115- if (!preorderNumber .containsKey (w )) {
116- // If the preorder number of w has not yet been assigned, recursively search w;
117- findStronglyConnectedComponentsRec (w , s , p , c , preorderNumber , component , componentCount );
118- } else {
119- // Otherwise, if w has not yet been assigned to a strongly connected component:
120- if (!component .containsKey (w )) {
121- // Repeatedly pop vertices from P until the top element of P has a preorder number less than or equal to the preorder number of w.
122- while (!p .isEmpty ()
123- && preorderNumber .getOrDefault (p .peek (), -1 ) > preorderNumber .get (w )) {
131+ // Post-order processing (all children of v have been visited)
132+ traversalStack .pop (); // Finished with v, pop it
133+ iterators .remove (v ); // Clean up iterator
134+
135+ if (!p .isEmpty () && p .peek () == v ) {
136+ Integer newComponent = componentCount .incrementAndGet ();
137+ while (true ) {
138+ T popped = s .pop ();
139+ component .put (popped , newComponent );
140+ if (popped == v ) {
141+ break ;
142+ }
143+ }
124144 p .pop ();
125145 }
126146 }
127147 }
128148 }
129- // 4. If v is the top element of P:
130- if (!p .isEmpty () && p .peek () == v ) {
131- // Pop vertices from S until v has been popped, and assign the popped vertices to a new component.
132- Integer newComponent = componentCount .incrementAndGet ();
133- while (true ) {
134- T popped = s .pop ();
135- component .put (popped , newComponent );
136- if (popped == v ) {
137- break ;
138- }
139- }
140- // Pop v from P.
141- T popped = p .pop ();
142- assert popped == v ;
143- }
144-
145-
149+ return ImmutableSet .copyOf (Utils .inverseMapToSet (component ).values ());
146150 }
147151
148152 public String generateDotFile (List <T > nodes ) {
@@ -166,5 +170,4 @@ public String generateDotFile(List<T> nodes) {
166170 sb .append ("}\n " );
167171 return sb .toString ();
168172 }
169-
170173}
0 commit comments