private void StrongConnect(int v, int vertexCount, uint[] edgeBits, ref TarjanOutput output) { Debug.Assert((vertexIndex[v] & OnStackBit) == 0); vertexIndex[v] = index | OnStackBit; vertexLowLink[v] = index; index++; stack[stackEnd++] = v; // Stack Push // Consider successors of v for (int w = 0; w < vertexCount; w++) { // NOTE: Considered using FFS-bit operation, but that only moves O(|V|) down to O(|E|) on a per-32-bit-block basis (by block will always be O(|V|)), probably not worth it -AR // PERF: Validate that this is inlined by the JIT (I think it should be -AR) if (!edgeBits.IsEdge(vertexCount, v, w)) { continue; } if (vertexIndex[w] == 0) // "not visited" { // Successor w has not yet been visited; recurse on it StrongConnect(w, vertexCount, edgeBits, ref output); vertexLowLink[v] = System.Math.Min(vertexLowLink[v], vertexLowLink[w]); } else if ((vertexIndex[w] & OnStackBit) != 0) { // Successor w is in the stack and hence in the current strongly-connected-component vertexLowLink[v] = System.Math.Min(vertexLowLink[v], vertexIndex[w] & ~OnStackBit); } } // If v is a root node, pop the stack and generate a strongly-connected-component if ((vertexIndex[v] & ~OnStackBit) == vertexLowLink[v]) { int componentEnd = stackEnd; // <- rather than popping into a buffer, keep track of the top of the stack so we can return the range // Repeatedly pop until we pop off v: do { stackEnd--; // Stack Pop (stack[stackEnd] is what was popped) Debug.Assert((vertexIndex[v] & OnStackBit) != 0); vertexIndex[stack[stackEnd]] &= ~OnStackBit; // Clear stack bit } while(stack[stackEnd] != v); int count = componentEnd - stackEnd; Debug.Assert(count >= 1); // Write output: if (count > 1) { output.sccList[output.sccCount++] = new SCC(output.vertexCount, count); // Add Array.Copy(stack, stackEnd, output.vertexList, output.vertexCount, count); // AddRange output.vertexCount += count; } else // Lone node (don't bother tracking these as SCCs, and don't bother with the bulk copy) { output.vertexList[output.vertexCount++] = stack[stackEnd]; // Add } } }
public void FindStronglyConnectedComponents(int vertexCount, uint[] edgeBits, ref TarjanOutput output) { Debug.Assert(output.sccList.Length >= vertexCount / 3); // <- NOTE: we could check for "vertexCount/2" but having less space is an optimisation for when we don't have a bi-directional graph Debug.Assert(output.vertexList.Length >= vertexCount); // INITIALIZE: { index = 1; // <- 0 is "undefined" (so we can fast-clear our indicies) stackEnd = 0; if (stack == null || stack.Length < vertexCount) { // PERF: Convert this to use a single buffer, with sections reserved for each purpose, so there are no gaps // Also interleave lowlink and index. And could use short instead of int. All probably overkill. // NOTE: Lazy over-allocate here, to avoid reallocation stack = new int[vertexCount * 2]; // <- the maximum size for the stack is vertexCount vertexIndex = new uint[vertexCount * 2]; vertexLowLink = new uint[vertexCount * 2]; } else // <- don't have to clear if we just got fresh memory from .NET { Array.Clear(vertexIndex, 0, vertexCount); // <- only have to clear as much as we'll use here... // NOTE: don't need to inialize vertexLowLink or stack } output.sccCount = 0; output.vertexCount = 0; } // RUN: { for (int v = 0; v < vertexCount; v++) { if (vertexIndex[v] == 0) // "not visited" { StrongConnect(v, vertexCount, edgeBits, ref output); } } } Debug.Assert(output.vertexCount == vertexCount); }
SCC[] bestSCCs; // count is a local /// <summary>NOTE: Returns a reference to an internal array which is cleared on subsequent sorts. May be longer than the passed in vertexCount - excess data is garbage.</summary> public int[] Sort(int vertexCount, uint[] edgeBits, Action <int, int> removedEdge) { // // INITIALIZE: // { if (primaryVertices == null || primaryVertices.Length < vertexCount) { // NOTE: Lazy over-allocate here, to avoid reallocation primaryVertices = new int[vertexCount * 2]; primarySCCStack = new SCC[(vertexCount * 2) / 3]; // worst-case capacity required is that every vertex triplet is an SCC // Secondary stage (NOTE: worst-case is that the whole of primary is a SCC, so this must be the same size) secondaryEdgeBits = EdgeBits.Create(vertexCount * 2); // NOTE: This allocates 4x as much memory as was passed in for edges secondaryVertices = new int[vertexCount * 2]; secondarySCCs = new SCC[(vertexCount * 2) / 3]; bestVertices = new int[vertexCount * 2]; bestSCCs = new SCC[(vertexCount * 2) / 3]; tempOutput = new int[vertexCount * 2]; } else { // NOTE: Nothing to clear (all items have counts and get re-initialized up to their count upon usage) } } // // RUN: // { // Primary: // -------- TarjanOutput primaryOutput = new TarjanOutput(); primaryOutput.sccList = primarySCCStack; primaryOutput.vertexList = primaryVertices; ta.FindStronglyConnectedComponents(vertexCount, edgeBits, ref primaryOutput); // Secondary: (this is a greedy search) // ---------- // Try to resolve any SCCs remaining in the output // Note that changes to a SCC do not affect the ordering of the surrounding DAG while (primaryOutput.sccCount > 0) { SCC workingSCC = primaryOutput.sccList[--primaryOutput.sccCount]; // Pop // The secondary stage uses indirect indices (ie: vertex number in primary = scc[vertex number in secondary]) // This allows the secondary stage to use implicit vertex numbers (0 to N-1, rather than having to access workingSCC) // This requires rebuilding edges to match this indexing scheme (secondaryEdge[v, w] = primaryEdge[scc[v], scc[w]]) // But this is desireable, because the access pattern into the primary edges would be cache-unfriendly // Also secondary is generally much smaller than primary (because it works on individual SCCs, which are usually relatively small) // Rebuild edge bits: int v, w; // v is "from", w is "to" for (v = 0; v < workingSCC.count; v++) { for (w = 0; w < workingSCC.count; w++) { secondaryEdgeBits.ChangeEdgeBit(workingSCC.count, v, w, edgeBits.GetEdgeBit(vertexCount, primaryVertices[workingSCC.index + v], primaryVertices[workingSCC.index + w])); } } int bestSCCCount = 0; int bestSCCMaximumSize = workingSCC.count; // The size of the largest SCCs <- used to select "best" (reduces size of O(N^2) problem -- unverified -AR) int bestSCCCumulativeSize = workingSCC.count; // Number of vertices in all SCCs <- used as a tie-break (reduces size of O(N) problem -- unverified -AR) // These are for logging int bestRemovedV = 0; int bestRemovedW = 0; // Try to find the best constraint (ie: edge) to remove: for (v = 0; v < workingSCC.count; v++) { for (w = 0; w < workingSCC.count; w++) { if (secondaryEdgeBits.IsEdge(workingSCC.count, v, w)) // PERF: Consider only looking at "backwards" edges (requires primary input is spatially sorted, then compare primary indices) { TarjanOutput secondaryOutput = new TarjanOutput(); secondaryOutput.sccList = secondarySCCs; secondaryOutput.vertexList = secondaryVertices; secondaryEdgeBits.ClearEdge(workingSCC.count, v, w); // Temporarly remove an edge (see if it improves the result) ta.FindStronglyConnectedComponents(workingSCC.count, secondaryEdgeBits, ref secondaryOutput); int secondarySCCMaximumSize = 0; int secondarySCCCumulativeSize = 0; for (int i = 0; i < secondaryOutput.sccCount; i++) { int count = secondaryOutput.sccList[i].count; if (count > secondarySCCMaximumSize) { secondarySCCMaximumSize = count; } secondarySCCCumulativeSize += count; } // NOTE: If we change to only looking at "backwards" edges, should probably add tie-break to select rearmost edge to remove // (May require pre-sorting the SCC, to handle the early-out case properly) if (secondaryOutput.sccCount == 0 || secondarySCCMaximumSize < bestSCCMaximumSize || (secondarySCCMaximumSize == bestSCCMaximumSize && secondarySCCCumulativeSize < bestSCCCumulativeSize)) { bestRemovedV = v; bestRemovedW = w; bestSCCCount = secondaryOutput.sccCount; bestSCCMaximumSize = secondarySCCMaximumSize; bestSCCCumulativeSize = secondarySCCCumulativeSize; Swap(ref bestSCCs, ref secondarySCCs); Swap(ref bestVertices, ref secondaryVertices); } if (secondaryOutput.sccCount == 0) { goto earlyOut; // Found optimal solution } secondaryEdgeBits.SetEdge(workingSCC.count, v, w); // Restore Edge } } } if (bestSCCMaximumSize == workingSCC.count) // Failed to find any improvement for this SCC - give up! { // Just leave the primary output in its original order // NOTE: If we change to sorted input (for "backwards" edges), consider re-sorting this SCC in output continue; } earlyOut: // skips the above // Notify any debug output that an edge was removed. NOTE: Before the SCC in primary gets re-ordered! if (removedEdge != null) { removedEdge(primaryVertices[workingSCC.index + bestRemovedV], primaryVertices[workingSCC.index + bestRemovedW]); } // Apply the new ordering for (int i = 0; i < workingSCC.count; i++) { tempOutput[i] = primaryVertices[workingSCC.index + bestVertices[i]]; } Array.Copy(tempOutput, 0, primaryVertices, workingSCC.index, workingSCC.count); // Transfer new SCCs to primary: for (int i = 0; i < bestSCCCount; i++) { primaryOutput.sccList[primaryOutput.sccCount++] = new SCC(workingSCC.index + bestSCCs[i].index, bestSCCs[i].count); } } // At this point, primarySCC stack is empty -- we're done return(primaryVertices); } }