| | 1 | | using MoreStructures.Graphs.Visitor; |
| | 2 | |
|
| | 3 | | namespace MoreStructures.Graphs.StronglyConnectedComponents; |
| | 4 | |
|
| | 5 | | /// <summary> |
| | 6 | | /// An efficient implementation of <see cref="ISccFinder"/>, which runs a single DFS on the inverted graph to calculate |
| | 7 | | /// post orders, and then uses the post orders to identify sink SCC. |
| | 8 | | /// </summary> |
| | 9 | | /// <remarks> |
| | 10 | | /// <para id="advantages"> |
| | 11 | | /// ADVANTANGES AND DISADVANTAGES |
| | 12 | | /// <br/> |
| | 13 | | /// - Compared to the simple approach of <see cref="NaiveSccFinder"/>, this implementation requires more complex |
| | 14 | | /// operations, such as reversing the graph, generating and storing post order values. |
| | 15 | | /// <br/> |
| | 16 | | /// - However, it has better runtime, approaching optimality, since it runs DFS only once, and not for every vertex |
| | 17 | | /// of the graph. |
| | 18 | | /// </para> |
| | 19 | | /// <para id="algorithm"> |
| | 20 | | /// ALGORITHM |
| | 21 | | /// <br/> |
| | 22 | | /// - The algorithm is based on the observation that the DFS on any vertex of a sink SCC is the sink SCC itself. |
| | 23 | | /// <br/> |
| | 24 | | /// - So the goal at each iteration is to efficiently find a vertex which belongs to a sink SCC and run DFS on it. |
| | 25 | | /// <br/> |
| | 26 | | /// - Such a vertex v can be found by running a single DFS on the reverse graph (graph with the same vertices and |
| | 27 | | /// reversed orientation of edges) and taking the vertex with the highest post order value. |
| | 28 | | /// <br/> |
| | 29 | | /// - After having identified v, DFS is run on v to find the sink SCC S, v is in. |
| | 30 | | /// <br/> |
| | 31 | | /// - All found vertices are assigned the same SCC S: a list of v items SCCs is initialized to store the result and |
| | 32 | | /// SCCs[i] is set to S to indicate the vertex i is in the SCC with label S. |
| | 33 | | /// <br/> |
| | 34 | | /// - Then the operation is repeated, identifing the vertex v with the highest post order value, among the vertices |
| | 35 | | /// of the graph which haven't been assigned a SCC yet. |
| | 36 | | /// <br/> |
| | 37 | | /// - When there are no vertices left, the SCCs list is returned as result. |
| | 38 | | /// </para> |
| | 39 | | /// <para id="complexity"> |
| | 40 | | /// COMPLEXITY |
| | 41 | | /// <br/> |
| | 42 | | /// - <see cref="IGraph.Reverse"/> performance depends on the specific implementation of the graph. |
| | 43 | | /// <br/> |
| | 44 | | /// - Building and instrumenting the required <see cref="IVisitStrategy"/> instances and storing the post order |
| | 45 | | /// do not depend on the side of the input. |
| | 46 | | /// <br/> |
| | 47 | | /// - The output has size equal to the number of vertices in the graph. |
| | 48 | | /// <br/> |
| | 49 | | /// - DFS, which is at the core of this algorithm, is run in two stages. |
| | 50 | | /// <br/> |
| | 51 | | /// - In the first stage it is executed just once, on the entire graph, calling |
| | 52 | | /// <see cref="IVisitStrategy.DepthFirstSearchOfGraph"/>. |
| | 53 | | /// <br/> |
| | 54 | | /// - In the second stage it is executed iteratively, on vertices not assigned an SCC yet, this time calling |
| | 55 | | /// <see cref="IVisitStrategy.DepthFirstSearchFromVertex"/>. |
| | 56 | | /// <br/> |
| | 57 | | /// - Because at each iteration chosen vertex belongs to a sink SCC (due to the fact that the vertex with the |
| | 58 | | /// highest remaining post order is selected), each vertex of the graph is explored at most once thoughout the |
| | 59 | | /// entire second stage. |
| | 60 | | /// <br/> |
| | 61 | | /// - Therefore, Time Complexity is O(Trvs + Tdfs) and Space Complexity is O(Srvs + Sdfs), where Trvs and Srvs are |
| | 62 | | /// the time and space required to reverse the provided <see cref="IGraph"/> and Tdfs and Sdfs are the time and |
| | 63 | | /// space required to run DFS via the <see cref="IVisitStrategy"/> provided by |
| | 64 | | /// <see cref="VisitStrategyBuilder"/>. |
| | 65 | | /// <br/> |
| | 66 | | /// - In typical implementations of <see cref="IGraph"/> and <see cref="IVisitStrategy"/>, Tdfs >= Trvs, and Tdfs |
| | 67 | | /// is O(v + e), which makes Time Complexity equals to O(v * Ta + e). |
| | 68 | | /// <br/> |
| | 69 | | /// - Space Complexity is generally at least O(v + e + Sa), if <see cref="IGraph.Reverse"/> just builds a proxy of |
| | 70 | | /// the provided graph, but can be O(v^2) for example if <see cref="IGraph"/> builds a separated transposed copy |
| | 71 | | /// of the adjacency matrix when reversing, and v^2 > v + e (very sparse graph). |
| | 72 | | /// </para> |
| | 73 | | /// </remarks> |
| | 74 | | public class SinkSccBasedSccFinder : ISccFinder |
| | 75 | | { |
| | 76 | | /// <summary> |
| | 77 | | /// A building function able to instantiate the <see cref="IVisitStrategy"/> to be used to run Depth First Searches |
| | 78 | | /// of the entire graph and from vertex, via <see cref="IVisitStrategy.DepthFirstSearchOfGraph(IGraph)"/> and |
| | 79 | | /// <see cref="IVisitStrategy.DepthFirstSearchFromVertex(IGraph, int)"/> respectively. |
| | 80 | | /// </summary> |
| 20 | 81 | | public Func<IVisitStrategy> VisitStrategyBuilder { get; } |
| | 82 | |
|
| | 83 | | /// <summary> |
| | 84 | | /// <inheritdoc cref="SinkSccBasedSccFinder"/> |
| | 85 | | /// </summary> |
| | 86 | | /// <param name="visitStrategyBuilder"> |
| | 87 | | /// <inheritdoc cref="VisitStrategyBuilder" path="/summary"/> |
| | 88 | | /// </param> |
| | 89 | | /// <remarks> |
| | 90 | | /// <inheritdoc cref="SinkSccBasedSccFinder"/> |
| | 91 | | /// </remarks> |
| 10 | 92 | | public SinkSccBasedSccFinder(Func<IVisitStrategy> visitStrategyBuilder) |
| 10 | 93 | | { |
| 10 | 94 | | VisitStrategyBuilder = visitStrategyBuilder; |
| 10 | 95 | | } |
| | 96 | |
|
| | 97 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 98 | | /// <remarks> |
| | 99 | | /// <inheritdoc cref="SinkSccBasedSccFinder"/> |
| | 100 | | /// </remarks> |
| | 101 | | public IList<int> Find(IGraph graph) |
| 10 | 102 | | { |
| 10 | 103 | | var reverseGraph = graph.Reverse(); |
| 10 | 104 | | var reverseVisitStrategy = VisitStrategyBuilder(); |
| | 105 | |
|
| 10 | 106 | | var numberOfVertices = graph.GetNumberOfVertices(); |
| 10 | 107 | | var verticesInPostOrder = new int[numberOfVertices]; |
| 10 | 108 | | var postOrderValue = 0; |
| 35 | 109 | | reverseVisitStrategy.VisitedVertex += (o, e) => verticesInPostOrder[postOrderValue++] = e.Vertex; |
| 10 | 110 | | MoreLinq.MoreEnumerable.Consume(reverseVisitStrategy.DepthFirstSearchOfGraph(reverseGraph)); |
| | 111 | |
|
| 10 | 112 | | var directVisitStrategy = VisitStrategyBuilder(); |
| | 113 | |
|
| 10 | 114 | | var scc = new int[numberOfVertices]; |
| 70 | 115 | | for (int i = 0; i < numberOfVertices; i++) |
| 25 | 116 | | scc[i] = -1; |
| | 117 | |
|
| 10 | 118 | | var currentScc = 0; |
| 70 | 119 | | for (var i = numberOfVertices - 1; i >= 0; i--) |
| 25 | 120 | | { |
| 25 | 121 | | var vertex = verticesInPostOrder[i]; |
| | 122 | |
|
| 25 | 123 | | if (scc[vertex] >= 0) |
| 8 | 124 | | continue; |
| | 125 | |
|
| 109 | 126 | | foreach (var reachableVertex in directVisitStrategy.DepthFirstSearchFromVertex(graph, vertex)) |
| 29 | 127 | | scc[reachableVertex] = currentScc; |
| | 128 | |
|
| 17 | 129 | | currentScc++; |
| 17 | 130 | | } |
| | 131 | |
|
| 10 | 132 | | return scc; |
| 10 | 133 | | } |
| | 134 | | } |