| | 1 | | using MoreLinq.Extensions; |
| | 2 | | using MoreStructures.Graphs.Visitor; |
| | 3 | |
|
| | 4 | | namespace MoreStructures.Graphs.ShortestDistance; |
| | 5 | |
|
| | 6 | | /// <summary> |
| | 7 | | /// An <see cref="IShortestDistanceFinder"/> which runs a BFS on the provided graph from the start vertex, to find the |
| | 8 | | /// shortest distance and a shortest path to the end vertex. |
| | 9 | | /// </summary> |
| | 10 | | /// <remarks> |
| | 11 | | /// <para id="algorithm"> |
| | 12 | | /// ALGORITHM |
| | 13 | | /// <br/> |
| | 14 | | /// - First, a <see cref="IVisitStrategy"/> is instantiated via the <see cref="VisitStrategyBuilder"/> provided in |
| | 15 | | /// the constructor. |
| | 16 | | /// <br/> |
| | 17 | | /// - The <see cref="IVisitStrategy"/> instance will be used to run |
| | 18 | | /// <see cref="IVisitStrategy.BreadthFirstSearchFromVertex"/> from the start vertex. |
| | 19 | | /// <br/> |
| | 20 | | /// - Unlike the Dijkstra algorithm, the BFS visit will traverse all vertices reachable from the start vertex by |
| | 21 | | /// breadth. Morever it never stops the BFS traversal, even when the current path can't possibly be shortest. |
| | 22 | | /// <br/> |
| | 23 | | /// - The visitor is instrumented with an event handler H, attached to both |
| | 24 | | /// <see cref="IVisitStrategy.VisitingVertex"/> and <see cref="IVisitStrategy.AlreadyVisitedVertex"/>. |
| | 25 | | /// <br/> |
| | 26 | | /// - H updates a dictionary SPs mapping each vertex v to its currently known shortest path SP from the start |
| | 27 | | /// vertex to v and the previous vertex of v in SP. |
| | 28 | | /// <br/> |
| | 29 | | /// - H also keeps a dictionary DSs mapping each vertex v to the <see cref="HashSet{T}"/> of its currently visited |
| | 30 | | /// downstream vertices, i.e. all vertices u visited from v during the BFS. |
| | 31 | | /// <br/> |
| | 32 | | /// - Every time H encounters a vertex v which is not yet in SPs (i.e. it doesn't have a registered shortest path |
| | 33 | | /// yet), it just adds it to SPs. |
| | 34 | | /// <br/> |
| | 35 | | /// - Every time H encounters a vertex v which is already is SPs (i.e. it has already a registered shortest path), |
| | 36 | | /// it adds it to SPs and then updates recursively the entries of SPs of all downstream vertices, using DSs. |
| | 37 | | /// <br/> |
| | 38 | | /// - After the BFS visit is concluded, there are two possible scenarios: either the end vertex has been discovered |
| | 39 | | /// by the visit or not. |
| | 40 | | /// <br/> |
| | 41 | | /// - If the end vertex has not been discovered, it means that there is no path at all from the start to the end |
| | 42 | | /// vertex. In that case, <see cref="int.MaxValue"/> and an emtpy <see cref="IList{T}"/> are returned as result. |
| | 43 | | /// <br/> |
| | 44 | | /// - If the end vertex has been discovered, <c>SPs[end]</c> gives the shortest distance. SPs can be navigated |
| | 45 | | /// backwards, until the start vertex, to find the path with the shortest distance. |
| | 46 | | /// </para> |
| | 47 | | /// <para id="complexity"> |
| | 48 | | /// COMPLEXITY |
| | 49 | | /// <br/> |
| | 50 | | /// - While building the <see cref="IVisitStrategy"/> instance has a complexity that depends on the user-provided |
| | 51 | | /// <see cref="VisitStrategyBuilder"/>, it doesn't generally depends on the size of the graph to be visited, and |
| | 52 | | /// can be considered O(1) work. |
| | 53 | | /// <br/> |
| | 54 | | /// - Instrumenting the <see cref="IVisitStrategy"/> instance is also a constant-time operation. |
| | 55 | | /// <br/> |
| | 56 | | /// - Running the handler for every visited and already visited vertex, however, requires updating vertices |
| | 57 | | /// downstream, every time an entry SPs is updated, i.e. when a smaller distance from the start is found. That |
| | 58 | | /// affects the performance of the BFS execution, increasing its Time Complexity. |
| | 59 | | /// <br/> |
| | 60 | | /// - Because the BFS doesn't stop when the end vertex is found (as there may be longer paths with shorter distance |
| | 61 | | /// from the start vertex), the downstream vertices to be recursively updated can cover then entire graph (i.e. |
| | 62 | | /// O(v) vertices). |
| | 63 | | /// <br/> |
| | 64 | | /// - The complexity of the BFS visit depends on the actual <see cref="IVisitStrategy"/> implementation used. |
| | 65 | | /// <br/> |
| | 66 | | /// - Rebuilding and reversing the path takes time and space proportional to the length of the path, which in the |
| | 67 | | /// worst case can be O(v) where v is the number of vertices of the graph. |
| | 68 | | /// <br/> |
| | 69 | | /// - Therefore, Time Complexity is O(Tbfs * v + v) and Space Complexity is O(Sbfs + v^2). |
| | 70 | | /// <br/> |
| | 71 | | /// - If <see cref="FullyIterativeHashSetBasedGraphVisit"/> or <see cref="FullyRecursiveHashSetBasedGraphVisit"/> |
| | 72 | | /// visits are used, with an <see cref="IGraph"/> retrieving neighbors in constant time, Time Complexity becomes |
| | 73 | | /// O((v + e) * v) and O(v^2 + e). |
| | 74 | | /// <br/> |
| | 75 | | /// - On dense graphs, Time Complexity is O(v^3) and Space Complexity is O(v^2). |
| | 76 | | /// </para> |
| | 77 | | /// </remarks> |
| | 78 | | public class BfsBasedShortestDistanceFinder : IShortestDistanceFinder |
| | 79 | | { |
| | 80 | | /// <summary> |
| | 81 | | /// A building function able to instantiate the <see cref="IVisitStrategy"/> to be used to find the shortest |
| | 82 | | /// distance, by running a Breadth First Searches from the start vertex via |
| | 83 | | /// <see cref="IVisitStrategy.BreadthFirstSearchFromVertex(IGraph, int)"/>. |
| | 84 | | /// </summary> |
| 22 | 85 | | public Func<IVisitStrategy> VisitStrategyBuilder { get; } |
| | 86 | |
|
| | 87 | | /// <summary> |
| | 88 | | /// <inheritdoc cref="BfsBasedShortestDistanceFinder"/> |
| | 89 | | /// </summary> |
| | 90 | | /// <param name="visitStrategyBuilder"> |
| | 91 | | /// <inheritdoc cref="VisitStrategyBuilder" path="/summary"/> |
| | 92 | | /// </param> |
| | 93 | | /// <remarks> |
| | 94 | | /// <inheritdoc cref="BfsBasedShortestDistanceFinder"/> |
| | 95 | | /// </remarks> |
| 26 | 96 | | public BfsBasedShortestDistanceFinder(Func<IVisitStrategy> visitStrategyBuilder) |
| 26 | 97 | | { |
| 26 | 98 | | VisitStrategyBuilder = visitStrategyBuilder; |
| 26 | 99 | | } |
| | 100 | |
|
| | 101 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 102 | | /// <remarks> |
| | 103 | | /// <inheritdoc cref="BfsBasedShortestDistanceFinder"/> |
| | 104 | | /// </remarks> |
| | 105 | | public (int, IList<int>) Find(IGraph graph, IGraphDistances distances, int start, int end) |
| 26 | 106 | | { |
| 26 | 107 | | ShortestDistanceFinderHelper.ValidateParameters(graph, start, end); |
| | 108 | |
|
| 22 | 109 | | var bestPreviouses = new BestPreviouses(new() { [start] = new(0, -1) }); |
| 22 | 110 | | var downstreamVertices = new Dictionary<int, HashSet<int>>(); |
| | 111 | |
|
| 22 | 112 | | var visitor = VisitStrategyBuilder(); |
| 22 | 113 | | visitor.VisitingVertex += |
| 135 | 114 | | (o, e) => UpdateBestPreviousAndDownstreamVertices(o, e, distances, bestPreviouses, downstreamVertices); |
| 22 | 115 | | visitor.AlreadyVisitedVertex += |
| 54 | 116 | | (o, e) => UpdateBestPreviousAndDownstreamVertices(o, e, distances, bestPreviouses, downstreamVertices); |
| | 117 | |
|
| 22 | 118 | | visitor |
| 22 | 119 | | .BreadthFirstSearchFromVertex(graph, start) |
| 22 | 120 | | .Consume(); |
| | 121 | |
|
| 22 | 122 | | if (!bestPreviouses.Values.ContainsKey(end)) |
| 3 | 123 | | return (int.MaxValue, Array.Empty<int>()); |
| | 124 | |
|
| 19 | 125 | | var shortestDistance = bestPreviouses.Values[end].DistanceFromStart; |
| 19 | 126 | | var shortestPath = ShortestDistanceFinderHelper.BuildShortestPath(end, bestPreviouses); |
| | 127 | |
|
| 19 | 128 | | return (shortestDistance, shortestPath); |
| 22 | 129 | | } |
| | 130 | |
|
| | 131 | | private static void UpdateBestPreviousAndDownstreamVertices( |
| | 132 | | object? sender, VisitEventArgs eventArgs, |
| | 133 | | IGraphDistances distances, BestPreviouses bestPreviouses, Dictionary<int, HashSet<int>> downstreamVertices) |
| 164 | 134 | | { |
| 164 | 135 | | var current = eventArgs.Vertex; |
| 164 | 136 | | if (eventArgs.PreviousVertex is not int previous) |
| 22 | 137 | | return; |
| | 138 | |
|
| 142 | 139 | | if (!downstreamVertices.ContainsKey(previous)) |
| 92 | 140 | | downstreamVertices[previous] = new HashSet<int> { current }; |
| | 141 | | else |
| 50 | 142 | | downstreamVertices[previous].Add(current); |
| | 143 | |
|
| 142 | 144 | | var distanceFromStartToPrevious = bestPreviouses.Values[previous].DistanceFromStart; |
| 142 | 145 | | var distanceFromPreviousToCurrent = distances[(previous, current)]; |
| 142 | 146 | | var distanceFromStartToCurrent = distanceFromStartToPrevious + distanceFromPreviousToCurrent; |
| | 147 | |
|
| 142 | 148 | | if (!bestPreviouses.Values.ContainsKey(current)) |
| 91 | 149 | | { |
| 91 | 150 | | bestPreviouses.Values[current] = new(distanceFromStartToCurrent, previous); |
| 91 | 151 | | } |
| 51 | 152 | | else if (bestPreviouses.Values[current].DistanceFromStart > distanceFromStartToCurrent) |
| 31 | 153 | | { |
| 31 | 154 | | bestPreviouses.Values[current] = new(distanceFromStartToCurrent, previous); |
| | 155 | |
|
| | 156 | | // Update all vertices downstream (if any), which have already entries in bestPreviouses |
| 31 | 157 | | if (downstreamVertices.ContainsKey(current)) |
| 83 | 158 | | foreach (var downstreamVertex in downstreamVertices[current]) |
| 19 | 159 | | UpdateBestPreviousAndDownstreamVertices( |
| 19 | 160 | | sender, new(downstreamVertex, eventArgs.ConnectedComponent, current), |
| 19 | 161 | | distances, bestPreviouses, downstreamVertices); |
| 31 | 162 | | } |
| 164 | 163 | | } |
| | 164 | | } |