blob: e8a775ceb032652242cdd52e9e5c44b4c8d9e8df [file] [log] [blame]
// 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<List<T>> stronglyConnectedComponents<T extends Object>(
Iterable<T> nodes,
Iterable<T> Function(T) edges, {
bool Function(T, T)? equals,
int Function(T)? hashCode,
}) {
final result = <List<T>>[];
final lowLinks = HashMap<T, int>(equals: equals, hashCode: hashCode);
final indexes = HashMap<T, int>(equals: equals, hashCode: hashCode);
final onStack = HashSet<T>(equals: equals, hashCode: hashCode);
final nonNullEquals = equals ?? _defaultEquals;
var index = 0;
final lastVisited = Queue<T>();
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>[];
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<T> {
/// 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<T>? iterator;
_StackState(this.node, [this.iterator]);
}
bool _defaultEquals(Object a, Object b) => a == b;