// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:collection'; import 'dart:math' show min; /// Finds the strongly connected components of a directed graph using Tarjan's /// algorithm. /// /// The result will be a valid reverse topological order ordering of the /// strongly connected components. Components further from a root will appear in /// the result before the components which they are connected to. /// /// Nodes within a strongly connected component have no ordering guarantees, /// except that if the first value in [nodes] is a valid root, and is contained /// in a cycle, it will be the last element of that cycle. /// /// [nodes] must contain at least a root of every tree in the graph if there are /// disjoint subgraphs but it may contain all nodes in the graph if the roots /// are not known. /// /// If [equals] is provided, it is used to compare nodes in the graph. If /// [equals] is omitted, the node's own [Object.==] is used instead. /// /// Similarly, if [hashCode] is provided, it is used to produce a hash value /// for nodes to efficiently calculate the return value. If it is omitted, the /// key's own [Object.hashCode] is used. /// /// If you supply one of [equals] or [hashCode], you should generally also to /// supply the other. List> stronglyConnectedComponents( Iterable nodes, Iterable Function(T) edges, { bool Function(T, T)? equals, int Function(T)? hashCode, }) { final result = >[]; final lowLinks = HashMap(equals: equals, hashCode: hashCode); final indexes = HashMap(equals: equals, hashCode: hashCode); final onStack = HashSet(equals: equals, hashCode: hashCode); final nonNullEquals = equals ?? _defaultEquals; var index = 0; final lastVisited = Queue(); final stack = [for (final node in nodes) _StackState(node)]; outer: while (stack.isNotEmpty) { final state = stack.removeLast(); final node = state.node; var iterator = state.iterator; int lowLink; if (iterator == null) { if (indexes.containsKey(node)) continue; indexes[node] = index; lowLink = lowLinks[node] = index; index++; iterator = edges(node).iterator; // Nodes with no edges are always in their own component. if (!iterator.moveNext()) { result.add([node]); continue; } lastVisited.addLast(node); onStack.add(node); } else { lowLink = min(lowLinks[node]!, lowLinks[iterator.current]!); } do { final next = iterator.current; if (!indexes.containsKey(next)) { stack.add(_StackState(node, iterator)); stack.add(_StackState(next)); continue outer; } else if (onStack.contains(next)) { lowLink = lowLinks[node] = min(lowLink, indexes[next]!); } } while (iterator.moveNext()); if (lowLink == indexes[node]) { final component = []; T next; do { next = lastVisited.removeLast(); onStack.remove(next); component.add(next); } while (!nonNullEquals(next, node)); result.add(component); } } return result; } /// The state of a pass on a single node in Tarjan's Algorithm. /// /// This is used to perform the algorithm with an explicit stack rather than /// recursively, to avoid stack overflow errors for very large graphs. class _StackState { /// The node being inspected. final T node; /// The iterator traversing [node]'s edges. /// /// This is null if the node hasn't yet begun being traversed. final Iterator? iterator; _StackState(this.node, [this.iterator]); } bool _defaultEquals(Object a, Object b) => a == b;