/// <summary> /// Uses Kruskal's algorithm to build an MST spanning the mstNodes /// with the given edges as a list ordered by priority ascending. /// The edges don't need to contain only nodes given to this instance /// via constructor. /// O(|edges|) runtime. /// </summary> /// <param name="orderedEdges">Edges ordered by priority ascending.</param> /// <remarks> /// Both Span methods have quadratic runtime in the graph nodes. This one /// has a lower constant factor but needs to filter out unneeded edges (quadratic /// in all nodes), the other one doesn't need to do any filtering (quadratic in /// considered nodes) so if the mst nodes are generally only a very small /// portion of all nodes, use the other Span method, if not, use this one. /// </remarks> public IReadOnlyList <DirectedGraphEdge> Span(IEnumerable <DirectedGraphEdge> orderedEdges) { _spanningEdges = new PooledList <DirectedGraphEdge>(_mstNodes.Count); var set = new DisjointSet(_distances.CacheSize); var considered = new bool[_distances.CacheSize]; var toAddCount = _mstNodes.Count - 1; foreach (var t in _mstNodes) { considered[t] = true; } foreach (var current in orderedEdges) { var inside = current.Inside; var outside = current.Outside; // This condition is by far the bottleneck of the method. // (most likely because branch prediction can't predict the result) if (!considered[inside] | !considered[outside]) { continue; } if (set.Find(inside) == set.Find(outside)) { continue; } _spanningEdges.Add(current); set.Union(inside, outside); if (--toAddCount == 0) { break; } } return(_spanningEdges); }
/// <summary> /// Uses Prim's algorithm to build an MST spanning the mstNodes. /// O(|mstNodes|^2) runtime. /// </summary> /// <param name="startIndex">The node index to start from.</param> public IReadOnlyList <DirectedGraphEdge> Span(int startIndex) { var notIncluded = ArrayPool <int> .Shared.Rent(_mstNodes.Count); var notIncludedCount = 0; var isIncluded = ArrayPool <bool> .Shared.Rent(_distances.CacheSize); Array.Clear(isIncluded, 0, isIncluded.Length); _spanningEdges?.Dispose(); _spanningEdges = new PooledList <DirectedGraphEdge>(_mstNodes.Count); using (var adjacentEdgeQueue = new LinkedListPriorityQueue <DirectedGraphEdge>(100, _mstNodes.Count * _mstNodes.Count)) { InitializeDataStructures(startIndex, notIncluded, ref notIncludedCount, adjacentEdgeQueue); isIncluded[startIndex] = true; while (notIncludedCount > 0 && !adjacentEdgeQueue.IsEmpty) { int newIn; DirectedGraphEdge shortestEdge; // Dequeue and ignore edges that are already inside the MST. // Add the first one that is not. do { shortestEdge = adjacentEdgeQueue.Dequeue(); newIn = shortestEdge.Outside; } while (isIncluded[newIn]); _spanningEdges.Add(shortestEdge); isIncluded[newIn] = true; // Find all newly adjacent edges and enqueue them. IncludeNode(notIncluded, ref notIncludedCount, adjacentEdgeQueue, newIn); } } ArrayPool <bool> .Shared.Return(isIncluded); ArrayPool <int> .Shared.Return(notIncluded); return(_spanningEdges); }