Skip to content

Commit

Permalink
Add support for Kruskal's spanning tree algorithm, and cleanup existi…
Browse files Browse the repository at this point in the history
…ng code related to it.
  • Loading branch information
renggli committed Apr 27, 2024
1 parent 05a8ffd commit 1e1b6a7
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 31 deletions.
3 changes: 3 additions & 0 deletions lib/graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export 'src/graph/algorithms.dart' show AlgorithmsGraphExtension;
export 'src/graph/algorithms/a_star_search.dart' show AStarSearchIterable;
export 'src/graph/algorithms/dijkstra_search.dart' show DijkstraSearchIterable;
export 'src/graph/algorithms/dinic_max_flow.dart' show DinicMaxFlow;
export 'src/graph/algorithms/kruskal_spanning_tree.dart'
show kruskalSpanningTree;
export 'src/graph/algorithms/prim_spanning_tree.dart' show primSpanningTree;
export 'src/graph/algorithms/stoer_wagner_min_cut.dart' show StoerWagnerMinCut;
export 'src/graph/edge.dart' show Edge;
export 'src/graph/errors.dart' show GraphError;
Expand Down
52 changes: 35 additions & 17 deletions lib/src/graph/algorithms.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'package:collection/collection.dart';

import '../../functional.dart';
import '../comparator/constructors/natural.dart';
import '../functional/types/constant.dart';
import '../functional/types/predicate.dart';
import 'algorithms/a_star_search.dart';
import 'algorithms/dijkstra_search.dart';
import 'algorithms/dinic_max_flow.dart';
import 'algorithms/prims_min_spanning_tree.dart';
import 'algorithms/kruskal_spanning_tree.dart';
import 'algorithms/prim_spanning_tree.dart';
import 'algorithms/stoer_wagner_min_cut.dart';
import 'algorithms/tarjan_strongly_connected.dart';
import 'graph.dart';
Expand Down Expand Up @@ -107,26 +110,41 @@ extension AlgorithmsGraphExtension<V, E> on Graph<V, E> {
vertexStrategy: vertexStrategy ?? this.vertexStrategy,
);

/// Returns the minimum spanning graphs using Prim's algorithm. If the graph
/// is disconnected, a single graph containing only the nodes reachable from
/// the start vertex is returned.
/// Returns the spanning tree of the graph.
///
/// - [startVertex] is the root node of the new graph. If omitted, a random
/// node of the graph is picked.
/// - [edgeWeight] is a function function that returns the positive weight
/// between two edges. If no function is provided, the numeric edge value
/// or a constant weight of _1_ is used.
Graph<V, E> minSpanning({
/// - [startVertex] is the root node of the new graph. If specified, Prim's
/// algorithm is returning the spanning tree starting at that vertex;
/// disconnected parts of the graph will be missing. Otherwise Kruskal's
/// algorithm is used, which always returns all vertices of the graph.
///
/// - [edgeWeight] is a function that returns the weight between two edges. If
/// no function is provided, the numeric edge value or a constant weight of
/// _1_ is used.
///
/// - [weightComparator] is a function that compares two weights. If no
/// function is provided, the standard comparator is used yielding a
/// minimum spanning tree.
///
Graph<V, E> spanningTree({
V? startVertex,
num Function(V source, V target)? edgeWeight,
Comparator<num>? weightComparator,
StorageStrategy<V>? vertexStrategy,
}) =>
primsMinSpanningTree<V, E>(
this,
startVertex: startVertex,
edgeWeight: edgeWeight ?? _getDefaultEdgeValueOr(1),
vertexStrategy: vertexStrategy ?? this.vertexStrategy,
);
startVertex == null
? kruskalSpanningTree<V, E>(
this,
edgeWeight: edgeWeight ?? _getDefaultEdgeValueOr(1),
weightComparator: weightComparator ?? naturalComparable<num>,
vertexStrategy: vertexStrategy ?? this.vertexStrategy,
)
: primSpanningTree<V, E>(
this,
startVertex: startVertex,
edgeWeight: edgeWeight ?? _getDefaultEdgeValueOr(1),
weightComparator: weightComparator ?? naturalComparable<num>,
vertexStrategy: vertexStrategy ?? this.vertexStrategy,
);

/// Returns the strongly connected components in this graph. The
/// implementation uses the Tarjan's algorithm and runs in linear time.
Expand Down
58 changes: 58 additions & 0 deletions lib/src/graph/algorithms/kruskal_spanning_tree.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:collection/collection.dart';

import '../graph.dart';
import '../operations/copy.dart';
import '../strategy.dart';

/// Kruskal's algorithm to find the spanning tree in _O(E*log(E))_.
///
/// See https://en.wikipedia.org/wiki/Kruskal%27s_algorithm.
Graph<V, E> kruskalSpanningTree<V, E>(
Graph<V, E> graph, {
required num Function(V source, V target) edgeWeight,
required Comparator<num> weightComparator,
required StorageStrategy<V> vertexStrategy,
}) {
// Create an empty copy of the graph.
final result = graph.copy(empty: true);
result.addVertices(graph.vertices);

// Fetch and sort the edges, if any.
final edges = graph.edges.toList(growable: false);
if (edges.isEmpty) return result;
edges.sortByCompare(
(value) => edgeWeight(value.source, value.target),
weightComparator,
);

// State for each vertex.
final states = vertexStrategy.createMap<_State<V>>();
_State<V> getState(V vertex) =>
states.putIfAbsent(vertex, () => _State(vertex));

// Process all the edges.
for (final edge in edges) {
final source = getState(edge.source);
final target = getState(edge.target);
if (source.vertex != target.vertex) {
if (source.rank < target.rank) {
states[source.vertex] = target;
} else if (target.rank < source.rank) {
states[target.vertex] = source;
} else {
states[target.vertex] = source;
source.rank++;
}
result.addEdge(edge.source, edge.target, value: edge.value);
}
}

return result;
}

final class _State<V> {
_State(this.vertex);

final V vertex;
int rank = 0;
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import 'package:collection/collection.dart' show PriorityQueue;

import '../../comparator/modifiers/result_of.dart';
import '../edge.dart';
import '../graph.dart';
import '../operations/copy.dart';
import '../strategy.dart';

/// Prim's algorithm to find the minimum spanning tree in _O(E*log(V))_.
/// Prim's algorithm to find the spanning tree in _O(E*log(V))_.
///
/// See https://en.wikipedia.org/wiki/Prim%27s_algorithm.
Graph<V, E> primsMinSpanningTree<V, E>(
Graph<V, E> primSpanningTree<V, E>(
Graph<V, E> graph, {
required V? startVertex,
required num Function(V source, V target) edgeWeight,
required Comparator<num> weightComparator,
required StorageStrategy<V> vertexStrategy,
}) {
// Create an empty copy of the graph.
final result = graph.copy(empty: true);
if (graph.vertices.isEmpty) return result;

// Queue for the shortest remaining edges.
final queue = PriorityQueue<_State<V, E>>(_stateCompare);
final queue = PriorityQueue<_State<V, E>>(
weightComparator.onResultOf((state) => state.cost));
void addOutgoingEdgesToQueue(V vertex) {
result.addVertex(vertex);
for (final edge in graph.outgoingEdgesOf(vertex)) {
if (!result.vertices.contains(edge.target)) {
queue.add(_State<V, E>(edge, edgeWeight(edge.source, edge.target)));
Expand Down Expand Up @@ -50,6 +53,3 @@ final class _State<V, E> {
final Edge<V, E> edge;
final num cost;
}

int _stateCompare<V, E>(_State<V, E> a, _State<V, E> b) =>
a.cost.compareTo(b.cost);
7 changes: 7 additions & 0 deletions lib/src/graph/graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ abstract class Graph<V, E> with ToStringPrinter {
/// Adds a [vertex] to this graph.
void addVertex(V vertex);

/// Adds all [vertices] to this graph.
void addVertices(Iterable<V> vertices) {
for (final vertex in vertices) {
addVertex(vertex);
}
}

/// Adds an edge between [source] and [target] vertex. Optionally associates
/// the provided [value] with the edge. If the edge already exists, replaces
/// the existing edge data.
Expand Down
Loading

0 comments on commit 1e1b6a7

Please sign in to comment.