| | 1 | | using MoreStructures.PriorityQueues; |
| | 2 | | using MoreStructures.PriorityQueues.Extensions; |
| | 3 | |
|
| | 4 | | namespace MoreStructures.Graphs.ShortestDistance; |
| | 5 | |
|
| | 6 | | /// <summary> |
| | 7 | | /// A <see cref="IShortestDistanceFinder"/> implementation based on the Dijkstra algorithm. |
| | 8 | | /// </summary> |
| | 9 | | /// <remarks> |
| | 10 | | /// <para id="requirements"> |
| | 11 | | /// REQUIREMENTS |
| | 12 | | /// <br/> |
| | 13 | | /// - Dijkstra algorithm relies on the constraint that <b>edge distances are non-negative</b>. Therefore, there |
| | 14 | | /// can't be negative loops and adding edges will always result in longer distances. If the graph has negative |
| | 15 | | /// edges, consider using <see cref="BellmanFordShortestDistanceFinder"/> instead. |
| | 16 | | /// <br/> |
| | 17 | | /// - Moreover, given that the path P between two vertices u and v is optimal (i.e. the sum of distances over the |
| | 18 | | /// edges of the path is minimal), if w is a vertex in P, both the sub-path of P, P1, from u and w, and the |
| | 19 | | /// sub-path P2, from w to v are optimal. |
| | 20 | | /// <br/> |
| | 21 | | /// - The algorithm takes advantage of that, by starting from the start vertex s and finding the shortest distance |
| | 22 | | /// for a single vertex v per iteration: the one minimizing the total distance from the start vertex, via any of |
| | 23 | | /// the vertices for which the shortest distance has already been calculated. |
| | 24 | | /// <br/> |
| | 25 | | /// - The vertex processed at each iteration v may not be in the optimal path from s to e. However, the optimal |
| | 26 | | /// path from s to v is surely optimal. |
| | 27 | | /// <br/> |
| | 28 | | /// - If e is reachable from s, it will be reached at some point in the traversal. Otherwise, the visit will stop |
| | 29 | | /// and a <see cref="int.MaxValue"/> distance will be returned instead. |
| | 30 | | /// </para> |
| | 31 | | /// <para id="algorithm"> |
| | 32 | | /// ALGORITHM |
| | 33 | | /// <br/> |
| | 34 | | /// - A dictionary BP, mapping each vertex to its currently known shortest distance from the start and the previous |
| | 35 | | /// vertex in a path with such a distance is instantiated and initialized to only contains the start vertex s, |
| | 36 | | /// which is a distance 0 from itself via an empty path. |
| | 37 | | /// <br/> |
| | 38 | | /// - A set A of already added vertices is also instantiated and initialized to only contain s. |
| | 39 | | /// <br/> |
| | 40 | | /// - A priority queue PQ, storing ids of not yet added vertices by their known shortest total distance from s, is |
| | 41 | | /// also instantiated empty. |
| | 42 | | /// <br/> |
| | 43 | | /// - Then, the main loop of the algorithm is executed, until e is not in A. |
| | 44 | | /// <br/> |
| | 45 | | /// - Neighbors of the last vertex added to A, named hereafter lav, which are not yet in A, are discovered via |
| | 46 | | /// <see cref="IGraph.GetAdjacentVerticesAndEdges(int, bool)"/> and iterated over. |
| | 47 | | /// <br/> |
| | 48 | | /// - If the distance d1, of a neighbor v from s via lav, is strictly smaller than the shortest distance from s to |
| | 49 | | /// a known by BP, d2, both BP and PQ are updated with the new distance (PQ is updated via |
| | 50 | | /// <see cref="UpdatablePriorityQueueExtensions.PushOrUpdate{T}(IUpdatablePriorityQueue{T}, T, int)"/>). |
| | 51 | | /// <br/> |
| | 52 | | /// - After all the neighbors of lav have been processed, the closest vertex to s non-in A is extracted via a |
| | 53 | | /// <see cref="IPriorityQueue{T}.Pop"/> on PQ, if such a vertex exists. |
| | 54 | | /// <br/> |
| | 55 | | /// - If it does exist, such vertex is added to A and becomes the new lav. Otherwise, the loop is stopped. |
| | 56 | | /// <br/> |
| | 57 | | /// - After the loop terminates, if e is in BP, it means that a path, which is also shortest, from a to e has been |
| | 58 | | /// found, and can be reconstructed backwards by jumping links in BP. |
| | 59 | | /// <br/> |
| | 60 | | /// - Otherwise no path from s to e exists in the graph, and <see cref="int.MaxValue"/> and an empty path are |
| | 61 | | /// returned as a result. |
| | 62 | | /// </para> |
| | 63 | | /// <para id="complexity"> |
| | 64 | | /// COMPLEXITY |
| | 65 | | /// <br/> |
| | 66 | | /// - Initializations of the three data structures BP, A and PQ take a constant amount of work. |
| | 67 | | /// <br/> |
| | 68 | | /// - Each iteration in the main loop adds a new vertex to A, and only vertices not yet in A are processed. |
| | 69 | | /// <br/> |
| | 70 | | /// - Therefore, the number of iterations in the main loop is at most the number v of vertices in the graph. |
| | 71 | | /// <br/> |
| | 72 | | /// - At each iteration all edges outgoing from v are checked. |
| | 73 | | /// <br/> |
| | 74 | | /// - Because in the worst case all vertices of the graph need to be explored, to find the shortest distance to e, |
| | 75 | | /// the number of edges explored in total can be as high as the total number of edges e in the graph. |
| | 76 | | /// <br/> |
| | 77 | | /// - For each edge for which a shortest distance is found (in the worst case all e edges of the graph), both BP |
| | 78 | | /// and PQ have to be updated with the new shortest distance. |
| | 79 | | /// <br/> |
| | 80 | | /// - Updating BP is done in O(1). Updating PQ, however, has a complexity which depends on the specific |
| | 81 | | /// <see cref="IUpdatablePriorityQueue{T}"/> implementation used. |
| | 82 | | /// <br/> |
| | 83 | | /// - As an example, if a <see cref="PriorityQueues.BinaryHeap.BinaryHeapPriorityQueue{T}"/> is used, updating PQ |
| | 84 | | /// has logarithmic complexity over the number of items in PQ, which is at most v. So the processing of all |
| | 85 | | /// edges, across all iterations, takes time proportional to e * log(v). |
| | 86 | | /// <br/> |
| | 87 | | /// - Moreover, after all neighbors of each vertex are processed, a <see cref="IPriorityQueue{T}.Pop"/> is done on |
| | 88 | | /// PQ, to find the next vertex to add to A. This operation too is logarithmic with the number of items in PQ. |
| | 89 | | /// So the total cost of all pop operations, across all iterations, takes time proportional to v * log(v). |
| | 90 | | /// <br/> |
| | 91 | | /// - Therefore, when using a <see cref="PriorityQueues.BinaryHeap.BinaryHeapPriorityQueue{T}"/>, Time Complexity |
| | 92 | | /// is O((v + e) * log(v)) and Space Complexity is O(v), since all structures contain at most v items, of |
| | 93 | | /// constant size. |
| | 94 | | /// <br/> |
| | 95 | | /// - The Time Complexity may change when a different <see cref="IUpdatablePriorityQueue{T}"/> is used. |
| | 96 | | /// For instance, if a <see cref="PriorityQueues.FibonacciHeap.FibonacciHeapPriorityQueue{T}"/> is used, because |
| | 97 | | /// push and update operations are done in constant amortized time, the complexity is reduced to |
| | 98 | | /// O(e + v * log(v)), whereas when a <see cref="PriorityQueues.ArrayList.ArrayListPriorityQueue{T}"/> is used, |
| | 99 | | /// the complexity increases to O((v + e) * v). |
| | 100 | | /// </para> |
| | 101 | | /// </remarks> |
| | 102 | | public class DijkstraShortestDistanceFinder : IShortestDistanceFinder |
| | 103 | | { |
| 857 | 104 | | private Func<IUpdatablePriorityQueue<int>> PriorityQueueBuilder { get; } |
| | 105 | |
|
| | 106 | | /// <inheritdoc cref="DijkstraShortestDistanceFinder"/>. |
| | 107 | | /// <param name="priorityQueueBuilder"> |
| | 108 | | /// A builder of a <see cref="IUpdatablePriorityQueue{T}"/> of <see cref="int"/> values, used by the algorithm to |
| | 109 | | /// store edges with priority from the closest to the start, to the farthest. |
| | 110 | | /// </param> |
| 157 | 111 | | public DijkstraShortestDistanceFinder(Func<IUpdatablePriorityQueue<int>> priorityQueueBuilder) |
| 157 | 112 | | { |
| 157 | 113 | | PriorityQueueBuilder = priorityQueueBuilder; |
| 157 | 114 | | } |
| | 115 | |
|
| | 116 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 117 | | /// <remarks> |
| | 118 | | /// <inheritdoc cref="DijkstraShortestDistanceFinder"/> |
| | 119 | | /// </remarks> |
| | 120 | | public (int, IList<int>) Find(IGraph graph, IGraphDistances distances, int start, int end) |
| 877 | 121 | | { |
| 877 | 122 | | ShortestDistanceFinderHelper.ValidateParameters(graph, start, end); |
| | 123 | |
|
| 857 | 124 | | var bestPreviouses = new BestPreviouses(new() { [start] = new(0, -1) }); |
| 857 | 125 | | var added = new HashSet<int>() { start }; |
| 857 | 126 | | var vertexes = PriorityQueueBuilder(); |
| 3339 | 127 | | var distancesFunc = (int edgeStart, int edgeEnd) => distances[(edgeStart, edgeEnd)]; |
| | 128 | |
|
| 857 | 129 | | var lastAdded = start; |
| 2868 | 130 | | while (lastAdded != end) |
| 2383 | 131 | | { |
| 2383 | 132 | | ShortestDistanceFinderHelper.RelaxOutgoingEdgesOfVertex( |
| 2383 | 133 | | graph, distancesFunc, bestPreviouses, added, vertexes, lastAdded); |
| | 134 | |
|
| 2368 | 135 | | if (vertexes.Count == 0) |
| 357 | 136 | | break; |
| | 137 | |
|
| 2011 | 138 | | lastAdded = vertexes.Pop().Item; |
| 2011 | 139 | | added.Add(lastAdded); |
| 2011 | 140 | | } |
| | 141 | |
|
| 842 | 142 | | if (!bestPreviouses.Values.ContainsKey(end)) |
| 357 | 143 | | return (int.MaxValue, Array.Empty<int>()); |
| | 144 | |
|
| 485 | 145 | | var shortestDistance = bestPreviouses.Values[end].DistanceFromStart; |
| 485 | 146 | | var shortestPath = ShortestDistanceFinderHelper.BuildShortestPath(end, bestPreviouses); |
| | 147 | |
|
| 485 | 148 | | return (shortestDistance, shortestPath); |
| 842 | 149 | | } |
| | 150 | | } |