| | 1 | | using MoreStructures.XifoStructures; |
| | 2 | |
|
| | 3 | | namespace MoreStructures.Graphs.Visitor; |
| | 4 | |
|
| | 5 | | /// <summary> |
| | 6 | | /// A <see cref="IVisitStrategy"/> implementation which uses an <see cref="HashSet{T}"/> of <see cref="int"/> to store |
| | 7 | | /// already visited vertices, while visiting the graph, and performs the visit iteratively, using a |
| | 8 | | /// <see cref="XStack{T}"/>. |
| | 9 | | /// </summary> |
| | 10 | | public class FullyIterativeHashSetBasedGraphVisit : DirectionableVisit |
| | 11 | | { |
| 34616 | 12 | | private record struct XifoFrame(int Vertex, bool Processed, int ConnectedComponent, int? PreviousVertex); |
| | 13 | |
|
| | 14 | | /// <inheritdoc path="//*[not(self::summary)]"/> |
| | 15 | | /// <summary> |
| | 16 | | /// <inheritdoc cref="FullyIterativeHashSetBasedGraphVisit"/> |
| | 17 | | /// </summary> |
| 424 | 18 | | public FullyIterativeHashSetBasedGraphVisit(bool directedGraph) : base(directedGraph) |
| 424 | 19 | | { |
| 424 | 20 | | } |
| | 21 | |
|
| | 22 | | /// <inheritdoc/> |
| | 23 | | protected override IEnumerable<(int, int)> DepthFirstSearchAndConnectedComponentsOfGraph(IGraph graph) |
| 108 | 24 | | { |
| 108 | 25 | | var stack = new XStack<XifoFrame>(); |
| 108 | 26 | | var alreadyVisited = new HashSet<int>(); // Populated by ProcessXifo |
| 108 | 27 | | var numberOfVertices = graph.GetNumberOfVertices(); |
| | 28 | |
|
| 108 | 29 | | var currentConnectedComponent = 0; |
| 968 | 30 | | for (var vertex = 0; vertex < numberOfVertices; vertex++) |
| 383 | 31 | | { |
| | 32 | | // Here RaiseAlreadyVisitedVertex should not be triggered, since the vertex is not been checked due to an |
| | 33 | | // incoming edge from another vertex, but rather to explore vertices in connected components which haven't |
| | 34 | | // been explored yet. |
| 383 | 35 | | if (alreadyVisited.Contains(vertex)) |
| 199 | 36 | | continue; |
| | 37 | |
|
| 184 | 38 | | stack.Push(new(vertex, false, currentConnectedComponent, null)); |
| 1139 | 39 | | while (stack.Count > 0) |
| 962 | 40 | | { |
| 1765 | 41 | | var maybeOutputItem = ProcessXifo(graph, stack, alreadyVisited, e => e.OrderByDescending(i => i)); |
| 955 | 42 | | if (maybeOutputItem >= 0) |
| 396 | 43 | | yield return (maybeOutputItem, currentConnectedComponent); |
| 955 | 44 | | } |
| | 45 | |
|
| 177 | 46 | | currentConnectedComponent++; |
| 177 | 47 | | } |
| 101 | 48 | | } |
| | 49 | |
|
| | 50 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 51 | | /// <remarks> |
| | 52 | | /// <para id="advantages"> |
| | 53 | | /// ADVANTAGES AND DISADVANTAGES |
| | 54 | | /// <br/> |
| | 55 | | /// <inheritdoc cref="DocFragments" path="/remarks/para[@id='fully-iterative-advantages']"/> |
| | 56 | | /// </para> |
| | 57 | | /// <para id="algorithm"> |
| | 58 | | /// ALGORITHM |
| | 59 | | /// <br/> |
| | 60 | | /// - The total number of vertices of the graph is found via <see cref="IGraph.GetNumberOfVertices"/>. |
| | 61 | | /// <br/> |
| | 62 | | /// - If such number is v, vertices are identified by integers from 0 to v - 1. |
| | 63 | | /// <br/> |
| | 64 | | /// - Iterate over all vertices, visiting all other vertices reachable from the current vertex i, in the same |
| | 65 | | /// way visit is performed by <see cref="IVisitStrategy.DepthFirstSearchFromVertex(IGraph, int)"/>. |
| | 66 | | /// <br/> |
| | 67 | | /// - All visits share the same <see cref="HashSet{T}"/> of visited vertices, so that, if a vertex has been |
| | 68 | | /// visited before, directly or via another vertex, it's not visited again. |
| | 69 | | /// <br/> |
| | 70 | | /// - Every time a new vertex is visited, such vertex is yielded into the output sequence. |
| | 71 | | /// </para> |
| | 72 | | /// <para id="complexity"> |
| | 73 | | /// COMPLEXITY |
| | 74 | | /// <br/> |
| | 75 | | /// - The complexity of retrieving the total number of vertices depends on |
| | 76 | | /// <see cref="IGraph.GetNumberOfVertices"/>. While being specific to the <see cref="IGraph"/> |
| | 77 | | /// implementation being used, all implementation provide O(1) runtime for such operation. |
| | 78 | | /// <br/> |
| | 79 | | /// - The <see cref="HashSet{T}"/> shared across all visits ensures that each vertex is visited at most once. |
| | 80 | | /// <br/> |
| | 81 | | /// - Because each vertex is visited at most once throughout the entire execution, edges are visited at most |
| | 82 | | /// once, when edge direction is taken into account during the visit, and twice, when it is not. |
| | 83 | | /// <br/> |
| | 84 | | /// - Thoughout the entire execution, a single vertex in the stack is processed at a time, while the stack can |
| | 85 | | /// grow to contain up to e (directed edges) or 2 * e items (undirected edges). |
| | 86 | | /// <br/> |
| | 87 | | /// - Therefore, Time Complexity is O(v * Ta + e) and Space Complexity is O(v + e + Sa), where v is the number |
| | 88 | | /// of vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 89 | | /// neighborhood of a given vertex. |
| | 90 | | /// </para> |
| | 91 | | /// <inheritdoc |
| | 92 | | /// cref="FullyRecursiveHashSetBasedGraphVisit.DepthFirstSearchOfGraph" |
| | 93 | | /// path="/remarks/para[@id='complexity-and-events']"/> |
| | 94 | | /// </remarks> |
| 63 | 95 | | public override IEnumerable<int> DepthFirstSearchOfGraph(IGraph graph) => base.DepthFirstSearchOfGraph(graph); |
| | 96 | |
|
| | 97 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 98 | | /// <remarks> |
| | 99 | | /// <para id="advantages"> |
| | 100 | | /// ADVANTAGES AND DISADVANTAGES |
| | 101 | | /// <br/> |
| | 102 | | /// <inheritdoc cref="DocFragments" path="/remarks/para[@id='fully-iterative-advantages']"/> |
| | 103 | | /// </para> |
| | 104 | | /// <para id="algorithm"> |
| | 105 | | /// ALGORITHM |
| | 106 | | /// <br/> |
| | 107 | | /// - The algorithm is a simple variation of <see cref="DepthFirstSearchOfGraph(IGraph)"/>, with the |
| | 108 | | /// differences explained in <see cref="FullyRecursiveHashSetBasedGraphVisit.ConnectedComponents(IGraph)"/>. |
| | 109 | | /// </para> |
| | 110 | | /// <para id="complexity"> |
| | 111 | | /// COMPLEXITY |
| | 112 | | /// <br/> |
| | 113 | | /// - The analysis of complexity is done in |
| | 114 | | /// <see cref="FullyRecursiveHashSetBasedGraphVisit.ConnectedComponents(IGraph)"/>. |
| | 115 | | /// <br/> |
| | 116 | | /// - Time Complexity is O(v * Ta + e) and Space Complexity is O(v + e + Sa), where v is the number of |
| | 117 | | /// vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 118 | | /// neighborhood of a given vertex. |
| | 119 | | /// </para> |
| | 120 | | /// <inheritdoc |
| | 121 | | /// cref="FullyRecursiveHashSetBasedGraphVisit.DepthFirstSearchOfGraph" |
| | 122 | | /// path="/remarks/para[@id='complexity-and-events']"/>/// |
| | 123 | | /// </remarks> |
| 29 | 124 | | public override IDictionary<int, int> ConnectedComponents(IGraph graph) => base.ConnectedComponents(graph); |
| | 125 | |
|
| | 126 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 127 | | /// <remarks> |
| | 128 | | /// <para id="advantages"> |
| | 129 | | /// ADVANTAGES AND DISADVANTAGES |
| | 130 | | /// <br/> |
| | 131 | | /// <inheritdoc cref="DocFragments" path="/remarks/para[@id='fully-iterative-advantages']"/> |
| | 132 | | /// </para> |
| | 133 | | /// <para id="algorithm"> |
| | 134 | | /// ALGORITHM |
| | 135 | | /// <br/> |
| | 136 | | /// - The algorithm closely resembles to |
| | 137 | | /// <see cref="FullyRecursiveHashSetBasedGraphVisit.DepthFirstSearchFromVertex(IGraph, int)"/>, |
| | 138 | | /// the only difference being that the state of the visit is stored into a <see cref="XStack{T}"/>, |
| | 139 | | /// instantiated in the heap, rather than in the call stack. |
| | 140 | | /// <br/> |
| | 141 | | /// - A <see cref="XStack{T}"/> is used, and not a <see cref="XQueue{T}"/>, to preserve the order of visit by |
| | 142 | | /// depth, and not by breadth. |
| | 143 | | /// </para> |
| | 144 | | /// <para id="complexity"> |
| | 145 | | /// COMPLEXITY |
| | 146 | | /// <br/> |
| | 147 | | /// - As for <see cref="FullyRecursiveHashSetBasedGraphVisit.DepthFirstSearchFromVertex(IGraph, int)"/>, |
| | 148 | | /// Time Complexity is O(v * Ta + e) and Space Complexity is O(v + e + Sa), where v is the number of |
| | 149 | | /// vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 150 | | /// neighborhood of a given vertex. |
| | 151 | | /// </para> |
| | 152 | | /// <inheritdoc |
| | 153 | | /// cref="FullyRecursiveHashSetBasedGraphVisit.DepthFirstSearchOfGraph" |
| | 154 | | /// path="/remarks/para[@id='complexity-and-events']"/> |
| | 155 | | /// </remarks> |
| | 156 | | public override IEnumerable<int> DepthFirstSearchFromVertex(IGraph graph, int vertex) |
| 285 | 157 | | { |
| 285 | 158 | | var stack = new XStack<XifoFrame>(); |
| 285 | 159 | | var alreadyVisited = new HashSet<int>(); // Populated by ProcessXifo |
| 285 | 160 | | stack.Push(new(vertex, false, 0, null)); // Single connected component "0" |
| 1938 | 161 | | while (stack.Count > 0) |
| 3021 | 162 | | if (ProcessXifo(graph, stack, alreadyVisited, e => e.OrderByDescending(i => i)) is var v and >= 0) |
| 667 | 163 | | yield return v; |
| 285 | 164 | | } |
| | 165 | |
|
| | 166 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 167 | | /// <remarks> |
| | 168 | | /// <para id="advantages"> |
| | 169 | | /// ADVANTAGES AND DISADVANTAGES |
| | 170 | | /// <br/> |
| | 171 | | /// <inheritdoc cref="DocFragments" path="/remarks/para[@id='fully-iterative-advantages']"/> |
| | 172 | | /// </para> |
| | 173 | | /// <para id="algorithm"> |
| | 174 | | /// ALGORITHM |
| | 175 | | /// <br/> |
| | 176 | | /// - The algorithm closely resembles <see cref="DepthFirstSearchFromVertex(IGraph, int)"/>, the only |
| | 177 | | /// difference being that a <see cref="XQueue{T}"/> is used, and not a <see cref="XStack{T}"/>, to visit by |
| | 178 | | /// breadth, and not by depth. |
| | 179 | | /// </para> |
| | 180 | | /// <para id="complexity"> |
| | 181 | | /// COMPLEXITY |
| | 182 | | /// <br/> |
| | 183 | | /// - Same consideration about complexity as in <see cref="DepthFirstSearchFromVertex(IGraph, int)"/> apply. |
| | 184 | | /// What is different is just the order of visit, not edges or vertices visited. |
| | 185 | | /// <br/> |
| | 186 | | /// - Therefore, Time Complexity is O(v * Ta + e) and Space Complexity is O(v + e + Sa), where v is the number |
| | 187 | | /// of vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 188 | | /// neighborhood of a given vertex. |
| | 189 | | /// </para> |
| | 190 | | /// <inheritdoc |
| | 191 | | /// cref="FullyRecursiveHashSetBasedGraphVisit.DepthFirstSearchOfGraph" |
| | 192 | | /// path="/remarks/para[@id='complexity-and-events']"/> |
| | 193 | | /// </remarks> |
| | 194 | | public override IEnumerable<int> BreadthFirstSearchFromVertex(IGraph graph, int vertex) |
| 96 | 195 | | { |
| 96 | 196 | | var queue = new XQueue<XifoFrame>(); |
| 96 | 197 | | var alreadyVisited = new HashSet<int>(); // Populated by ProcessXifo |
| 96 | 198 | | queue.Push(new(vertex, false, 0, null)); // Single connected component "0" |
| 937 | 199 | | while (queue.Count > 0) |
| 1611 | 200 | | if (ProcessXifo(graph, queue, alreadyVisited, e => e.OrderBy(i => i)) is var v and >= 0) |
| 314 | 201 | | yield return v; |
| 90 | 202 | | } |
| | 203 | |
|
| | 204 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 205 | | /// <remarks> |
| | 206 | | /// The algorithm is very similar to <see cref="BreadthFirstSearchFromVertex(IGraph, int)"/>, with the only |
| | 207 | | /// difference that all vertices in the <paramref name="vertices"/> sequence are pushed to the queue, instead |
| | 208 | | /// of a single one. |
| | 209 | | /// <br/> |
| | 210 | | /// Because the vertices may or may not belong to different connected components, this algorithm returns "-1" |
| | 211 | | /// as connected component for all visited vertices. |
| | 212 | | /// <br/> |
| | 213 | | /// Time Complexity is O(v * Ta + e) and Space Complexity is O(v + e + Sa), exactly as for |
| | 214 | | /// <see cref="BreadthFirstSearchFromVertex(IGraph, int)"/>, since in the worst case the entire graph has to be |
| | 215 | | /// explored. |
| | 216 | | /// </remarks> |
| | 217 | | public override IEnumerable<int> BreadthFirstSearchFromVertices(IGraph graph, IEnumerable<int> vertices) |
| 105 | 218 | | { |
| 105 | 219 | | var queue = new XQueue<XifoFrame>(); |
| 105 | 220 | | var alreadyVisited = new HashSet<int>(); // Populated by ProcessXifo |
| 533 | 221 | | foreach (var vertex in vertices) |
| 109 | 222 | | queue.Push(new(vertex, false, -1, null)); |
| 951 | 223 | | while (queue.Count > 0) |
| 1583 | 224 | | if (ProcessXifo(graph, queue, alreadyVisited, e => e.OrderBy(i => i)) is var v and >= 0) |
| 296 | 225 | | yield return v; |
| 105 | 226 | | } |
| | 227 | |
|
| | 228 | | private int ProcessXifo( |
| | 229 | | IGraph graph, IXifoStructure<XifoFrame> xifo, HashSet<int> alreadyVisited, |
| | 230 | | Func<IEnumerable<int>, IEnumerable<int>> neighborsProcessor) |
| 4308 | 231 | | { |
| 4308 | 232 | | var (vertex, processed, connectedComponent, previousVertex) = xifo.Pop(); |
| | 233 | |
|
| 4308 | 234 | | if (processed) |
| 1644 | 235 | | { |
| 1644 | 236 | | RaiseVisitedVertex(new(vertex, connectedComponent, previousVertex)); |
| 1644 | 237 | | return -2; |
| | 238 | | } |
| | 239 | |
|
| | 240 | | // The following check is not done in the foreach loop of neighbors, to respect the order in which |
| | 241 | | // RaiseAlreadyVisitedVertex events are raised. Triggering a RaiseAlreadyVisitedVertex event directly in the |
| | 242 | | // loop below would produced an inverted sequence of events in DFS, where a XStack is used and children are |
| | 243 | | // pushed onto the stack in reversed order. |
| 2664 | 244 | | if (alreadyVisited.Contains(vertex)) |
| 991 | 245 | | { |
| 991 | 246 | | RaiseAlreadyVisitedVertex(new(vertex, connectedComponent, previousVertex)); |
| 984 | 247 | | return -1; |
| | 248 | | } |
| | 249 | |
|
| 1673 | 250 | | RaiseVisitingVertex(new(vertex, connectedComponent, previousVertex)); |
| | 251 | |
|
| 1673 | 252 | | alreadyVisited.Add(vertex); |
| 1673 | 253 | | var unexploredVertices = graph |
| 1673 | 254 | | .GetAdjacentVerticesAndEdges(vertex, DirectedGraph) |
| 3672 | 255 | | .Select(neighbor => neighbor.Vertex); |
| | 256 | |
|
| 1673 | 257 | | xifo.Push(new(vertex, true, connectedComponent, previousVertex)); |
| | 258 | |
|
| 9017 | 259 | | foreach (var unexploredVertex in neighborsProcessor(unexploredVertices)) |
| 1999 | 260 | | xifo.Push(new(unexploredVertex, false, connectedComponent, vertex)); |
| | 261 | |
|
| 1673 | 262 | | return vertex; |
| 4301 | 263 | | } |
| | 264 | | } |