예제 #1
0
        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
                }
            }
        }
예제 #2
0
        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);
            }
        }