From 83a5bf8c9a13edc8cb6758844c82182ac5643e06 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Sat, 1 Oct 2022 16:53:02 +0200 Subject: [PATCH] Graphs: Shortest Distance - A* and bidirectional A* (#227) --- .../DictionaryAdapterGraphDistancesTests.cs | 39 ++++++++++ .../MinimumSpanningTree/MstFinderTests.cs | 6 +- .../AStarShortestDistanceFinderTests.cs | 27 +++++++ ...ctionalAStarShortestDistanceFinderTests.cs | 27 +++++++ ...nalDijkstraShortestDistanceFinderTests.cs} | 0 ...tentialBasedShortestDistanceFinderTests.cs | 56 +++++++++++++ .../ShortestDistanceFinderTests.cs | 5 +- .../ShortestDistanceTreeFinderTests.cs | 9 ++- .../Graphs/DictionaryAdapterGraphDistances.cs | 26 +++++++ MoreStructures/Graphs/IGraphDistances.cs | 15 ++++ .../Graphs/MinimumSpanningTree/IMstFinder.cs | 4 +- .../MinimumSpanningTree/KruskalMstFinder.cs | 6 +- .../MinimumSpanningTree/PrimMstFinder.cs | 2 +- .../AStarShortestDistanceFinder.cs | 78 +++++++++++++++++++ .../BellmanFordShortestDistanceFinder.cs | 4 +- .../BfsBasedShortestDistanceFinder.cs | 6 +- ...idirectionalAStarShortestDistanceFinder.cs | 47 +++++++++++ ...rectionalDijkstraShortestDistanceFinder.cs | 4 +- .../DijkstraShortestDistanceFinder.cs | 4 +- .../IPotentialBasedShortestDistanceFinder.cs | 63 +++++++++++++++ .../IShortestDistanceFinder.cs | 2 +- .../PotentialBasedShortestDistanceFinder.cs | 64 +++++++++++++++ .../BellmanFordShortestDistanceTreeFinder.cs | 6 +- .../DijkstraShortestDistanceTreeFinder.cs | 2 +- .../IShortestDistanceTreeFinder.cs | 2 +- 25 files changed, 470 insertions(+), 34 deletions(-) create mode 100644 MoreStructures.Tests/Graphs/DictionaryAdapterGraphDistancesTests.cs create mode 100644 MoreStructures.Tests/Graphs/ShortestDistance/AStarShortestDistanceFinderTests.cs create mode 100644 MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinderTests.cs rename MoreStructures.Tests/Graphs/ShortestDistance/{BidirectionalDijkstraShortestDistanceFinderTests_WithFibonacciHeap.cs => BidirectionalDijkstraShortestDistanceFinderTests.cs} (100%) create mode 100644 MoreStructures.Tests/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinderTests.cs create mode 100644 MoreStructures/Graphs/DictionaryAdapterGraphDistances.cs create mode 100644 MoreStructures/Graphs/IGraphDistances.cs create mode 100644 MoreStructures/Graphs/ShortestDistance/AStarShortestDistanceFinder.cs create mode 100644 MoreStructures/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinder.cs create mode 100644 MoreStructures/Graphs/ShortestDistance/IPotentialBasedShortestDistanceFinder.cs create mode 100644 MoreStructures/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinder.cs diff --git a/MoreStructures.Tests/Graphs/DictionaryAdapterGraphDistancesTests.cs b/MoreStructures.Tests/Graphs/DictionaryAdapterGraphDistancesTests.cs new file mode 100644 index 00000000..313d41d0 --- /dev/null +++ b/MoreStructures.Tests/Graphs/DictionaryAdapterGraphDistancesTests.cs @@ -0,0 +1,39 @@ +using MoreStructures.Graphs; + +namespace MoreStructures.Tests.Graphs; + +[TestClass] +public class DictionaryAdapterGraphDistancesTests +{ + [TestMethod] + public void Indexer_TakesDataFromUnderlyingDictionary() + { + var dictionary = new Dictionary<(int, int), int> + { + [(0, 0)] = 0, + [(0, 1)] = 1, + [(0, 2)] = 2, + [(1, 0)] = 3, + }; + var graphDistances = new DictionaryAdapterGraphDistances(dictionary); + + foreach (var key in dictionary.Keys) + Assert.AreEqual(dictionary[key], graphDistances[key]); + } + + [TestMethod] + public void Indexer_RaisesExceptionWhenProvidedEdgeIsUnknown() + { + var dictionary = new Dictionary<(int, int), int> + { + [(0, 0)] = 0, + [(0, 1)] = 1, + [(0, 2)] = 2, + [(1, 0)] = 3, + }; + var graphDistances = new DictionaryAdapterGraphDistances(dictionary); + + Assert.ThrowsException(() => graphDistances[(1, 2)]); + Assert.ThrowsException(() => graphDistances[(2, 0)]); + } +} diff --git a/MoreStructures.Tests/Graphs/MinimumSpanningTree/MstFinderTests.cs b/MoreStructures.Tests/Graphs/MinimumSpanningTree/MstFinderTests.cs index ddf8151f..fb8d1854 100644 --- a/MoreStructures.Tests/Graphs/MinimumSpanningTree/MstFinderTests.cs +++ b/MoreStructures.Tests/Graphs/MinimumSpanningTree/MstFinderTests.cs @@ -76,7 +76,8 @@ public void Find_IsCorrect( var finder = FinderBuilder(); var distancesDict = starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second); - var mst = finder.Find(graph, distancesDict); + var graphDistances = new DictionaryAdapterGraphDistances(distancesDict); + var mst = finder.Find(graph, graphDistances); // If numberOfVertices == 0 => 0 edges in MST, 0 distinct vertices in MST, 0 connected components if (numberOfVertices > 1) @@ -119,7 +120,8 @@ public void Find_ThrowsExceptionIfTheGraphIsNotConnected( var finder = FinderBuilder(); var distancesDict = starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second); + var graphDistances = new DictionaryAdapterGraphDistances(distancesDict); - Assert.ThrowsException(() => finder.Find(graph, distancesDict), graphDescription); + Assert.ThrowsException(() => finder.Find(graph, graphDistances), graphDescription); } } diff --git a/MoreStructures.Tests/Graphs/ShortestDistance/AStarShortestDistanceFinderTests.cs b/MoreStructures.Tests/Graphs/ShortestDistance/AStarShortestDistanceFinderTests.cs new file mode 100644 index 00000000..3e999253 --- /dev/null +++ b/MoreStructures.Tests/Graphs/ShortestDistance/AStarShortestDistanceFinderTests.cs @@ -0,0 +1,27 @@ +using MoreStructures.Graphs; +using MoreStructures.Graphs.ShortestDistance; +using MoreStructures.PriorityQueues.BinomialHeap; + +namespace MoreStructures.Tests.Graphs.ShortestDistance; + +[TestClass] +public class AStarShortestDistanceFinderTests_WithoutHeuristic : DijkstraShortestDistanceFinderTests +{ + public AStarShortestDistanceFinderTests_WithoutHeuristic() + : base( + (numberOfVertices, edges) => new EdgeListGraph(numberOfVertices, edges), + () => new AStarShortestDistanceFinder(() => new UpdatableBinomialHeapPriorityQueue())) + { + } +} + +[TestClass] +public class AStarShortestDistanceFinderTests_WithHeuristic : PotentialBasedShortestDistanceFinderTests +{ + public AStarShortestDistanceFinderTests_WithHeuristic() + : base( + (numberOfVertices, edges) => new EdgeListGraph(numberOfVertices, edges), + () => new AStarShortestDistanceFinder(() => new UpdatableBinomialHeapPriorityQueue())) + { + } +} diff --git a/MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinderTests.cs b/MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinderTests.cs new file mode 100644 index 00000000..4a110cd2 --- /dev/null +++ b/MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinderTests.cs @@ -0,0 +1,27 @@ +using MoreStructures.Graphs; +using MoreStructures.Graphs.ShortestDistance; +using MoreStructures.PriorityQueues.BinomialHeap; + +namespace MoreStructures.Tests.Graphs.ShortestDistance; + +[TestClass] +public class BidirectionalAStarShortestDistanceFinderTests_WithoutHeuristic : DijkstraShortestDistanceFinderTests +{ + public BidirectionalAStarShortestDistanceFinderTests_WithoutHeuristic() + : base( + (numberOfVertices, edges) => new EdgeListGraph(numberOfVertices, edges), + () => new BidirectionalAStarShortestDistanceFinder(() => new UpdatableBinomialHeapPriorityQueue())) + { + } +} + +[TestClass] +public class BidirectionalAStarShortestDistanceFinderTests_WithHeuristic : PotentialBasedShortestDistanceFinderTests +{ + public BidirectionalAStarShortestDistanceFinderTests_WithHeuristic() + : base( + (numberOfVertices, edges) => new EdgeListGraph(numberOfVertices, edges), + () => new BidirectionalAStarShortestDistanceFinder(() => new UpdatableBinomialHeapPriorityQueue())) + { + } +} diff --git a/MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinderTests_WithFibonacciHeap.cs b/MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinderTests.cs similarity index 100% rename from MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinderTests_WithFibonacciHeap.cs rename to MoreStructures.Tests/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinderTests.cs diff --git a/MoreStructures.Tests/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinderTests.cs b/MoreStructures.Tests/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinderTests.cs new file mode 100644 index 00000000..359cc1a2 --- /dev/null +++ b/MoreStructures.Tests/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinderTests.cs @@ -0,0 +1,56 @@ +using MoreStructures.Graphs; +using MoreStructures.Graphs.ShortestDistance; + +namespace MoreStructures.Tests.Graphs.ShortestDistance; + +public abstract class PotentialBasedShortestDistanceFinderTests +{ + protected Func, IGraph> GraphBuilder { get; } + protected Func FinderBuilder { get; } + + protected PotentialBasedShortestDistanceFinderTests( + Func, IGraph> graphBuilder, Func finderBuilder) + { + GraphBuilder = graphBuilder; + FinderBuilder = finderBuilder; + } + + [DataRow("7 V, source to sink, same source to 1-chain and 3-chain merging to vertex to sink", 7, + new[] { 0, 0, 0, 1, 2, 3, 4, 5 }, + new[] { 1, 2, 6, 3, 4, 6, 5, 1 }, + new[] { 27, 3, 21, 3, 3, 3, 3, 3 }, + new int[] + { + 9, 8, 7, 6, 5, 4, 3, // Bad potentials: non-sensible values + 1, 1, 1, 1, 1, 1, 1, // Bad potentials: all equal values + 0, 3, 1, 4, 2, 3, 3, // Approx euclidean potentials calculated from vertex 0 + 3, 0, 3, 1, 2, 1, 1, // Approx euclidean potentials calculated from vertex 1 + 1, 3, 2, 1, 3, 2, 4, // Approx euclidean potentials calculated from vertex 2 + })] + [DataTestMethod] + public void Find_IsCorrect( + string graphDescription, int numberOfVertices, int[] starts, int[] ends, int[] distances, int[] potentials) + { + var graph = GraphBuilder(numberOfVertices, starts.Zip(ends).ToList()); + var graphDistances = new DictionaryAdapterGraphDistances( + starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second)); + + var finder = FinderBuilder(); + + for (var start = 0; start < numberOfVertices; start++) + { + for (var end = 0; end < numberOfVertices; end++) + { + for (var i = 0; i < potentials.Length; i += numberOfVertices) + { + var (distanceWithHeuristic, pathWithHeuristic) = + finder.Find(graph, graphDistances, v => potentials[i + v], start, end); + var (distanceWithoutHeuristic, pathWithoutHeuristic) = + finder.Find(graph, graphDistances, start, end); + Assert.AreEqual(distanceWithoutHeuristic, distanceWithHeuristic, graphDescription); + Assert.IsTrue(pathWithoutHeuristic.SequenceEqual(pathWithHeuristic), graphDescription); + } + } + } + } +} diff --git a/MoreStructures.Tests/Graphs/ShortestDistance/ShortestDistanceFinderTests.cs b/MoreStructures.Tests/Graphs/ShortestDistance/ShortestDistanceFinderTests.cs index 77485c0d..d5e5ece6 100644 --- a/MoreStructures.Tests/Graphs/ShortestDistance/ShortestDistanceFinderTests.cs +++ b/MoreStructures.Tests/Graphs/ShortestDistance/ShortestDistanceFinderTests.cs @@ -149,10 +149,11 @@ protected void TestGraph( int expectedDistance, int[] expectedPath) { var graph = GraphBuilder(numberOfVertices, starts.Zip(ends).ToList()); - var distancesDict = starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second); + var graphDistances = new DictionaryAdapterGraphDistances( + starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second)); var finder = FinderBuilder(); - var (distance, path) = finder.Find(graph, distancesDict, start, end); + var (distance, path) = finder.Find(graph, graphDistances, start, end); var message = $"{graphDescription} - Expected [{string.Join(", ", expectedPath)}], Actual: [{string.Join(", ", path)}]"; Assert.AreEqual(expectedDistance, distance, message); diff --git a/MoreStructures.Tests/Graphs/ShortestDistanceTree/ShortestDistanceTreeFinderTests.cs b/MoreStructures.Tests/Graphs/ShortestDistanceTree/ShortestDistanceTreeFinderTests.cs index 621db928..53c8c91c 100644 --- a/MoreStructures.Tests/Graphs/ShortestDistanceTree/ShortestDistanceTreeFinderTests.cs +++ b/MoreStructures.Tests/Graphs/ShortestDistanceTree/ShortestDistanceTreeFinderTests.cs @@ -36,10 +36,11 @@ protected void TestGraph( string graphDescription, int numberOfVertices, int[] starts, int[] ends, int[] distances, int start) { var graph = GraphBuilder(numberOfVertices, starts.Zip(ends).ToList()); - var distancesDict = starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second); + var graphDistances = new DictionaryAdapterGraphDistances( + starts.Zip(ends).Zip(distances).ToDictionary(t => t.First, t => t.Second)); var finder = FinderBuilder(); - var bestPreviouses = finder.FindTree(graph, distancesDict, start).Values; + var bestPreviouses = finder.FindTree(graph, graphDistances, start).Values; var bestDistances = from bp in bestPreviouses orderby bp.Key @@ -48,7 +49,7 @@ orderby bp.Key var singlePathFinder = SinglePathFinderBuilder(); var expectedBestDistances = Enumerable .Range(0, numberOfVertices) - .Select(v => singlePathFinder.Find(graph, distancesDict, start, v).Item1) + .Select(v => singlePathFinder.Find(graph, graphDistances, start, v).Item1) .Where(d => d < int.MaxValue); var message = @@ -80,7 +81,7 @@ orderby bp.Key Assert.IsTrue(path.First.Value == start); - var expectedPathDistance = path.Zip(path.Skip(1)).Sum(e => distancesDict[e]); + var expectedPathDistance = path.Zip(path.Skip(1)).Sum(e => graphDistances[e]); Assert.AreEqual(bestPreviouses[vertex].DistanceFromStart, expectedPathDistance); } } diff --git a/MoreStructures/Graphs/DictionaryAdapterGraphDistances.cs b/MoreStructures/Graphs/DictionaryAdapterGraphDistances.cs new file mode 100644 index 00000000..7f8cd910 --- /dev/null +++ b/MoreStructures/Graphs/DictionaryAdapterGraphDistances.cs @@ -0,0 +1,26 @@ +namespace MoreStructures.Graphs; + +/// +/// A retrieving distances from a , mapping +/// couples of values (ids of endpoints of each edge of the graph) to values +/// (edge distances). +/// +public class DictionaryAdapterGraphDistances : IGraphDistances +{ + private IDictionary<(int, int), int> Dictionary { get; } + + /// + /// + /// Retrieves the value from the underlying dictionary. + /// + public int this[(int edgeStart, int edgeEnd) edge] => Dictionary[edge]; + + /// + /// + /// + /// The mapping between edges and distances. + public DictionaryAdapterGraphDistances(IDictionary<(int, int), int> dictionary) + { + Dictionary = dictionary; + } +} diff --git a/MoreStructures/Graphs/IGraphDistances.cs b/MoreStructures/Graphs/IGraphDistances.cs new file mode 100644 index 00000000..726ed7d6 --- /dev/null +++ b/MoreStructures/Graphs/IGraphDistances.cs @@ -0,0 +1,15 @@ +namespace MoreStructures.Graphs; + +/// +/// Represents a mapping between edges of a and distances, in a spatial context, or weights, in +/// a more general setting. +/// +public interface IGraphDistances +{ + /// + /// Returns the distance, or weight, of the provided . + /// + /// The edge, to provide the distance of. + /// Any positive or negative number. + int this[(int edgeStart, int edgeEnd) edge] { get; } +} diff --git a/MoreStructures/Graphs/MinimumSpanningTree/IMstFinder.cs b/MoreStructures/Graphs/MinimumSpanningTree/IMstFinder.cs index a1fdef0e..889daf3b 100644 --- a/MoreStructures/Graphs/MinimumSpanningTree/IMstFinder.cs +++ b/MoreStructures/Graphs/MinimumSpanningTree/IMstFinder.cs @@ -48,12 +48,12 @@ public interface IMstFinder /// /// The , to find the MST of. /// - /// The dictionary mapping each edge of to its weight, which represents the "distance" + /// The mapping of each edge of to its weight, which represents the "distance" /// from the start vertex of the edge to the end vertex. /// /// The set of edges, in the form (source, target), identifying the MST. /// /// /// - public ISet<(int, int)> Find(IGraph graph, IDictionary<(int, int), int> distances); + public ISet<(int, int)> Find(IGraph graph, IGraphDistances distances); } diff --git a/MoreStructures/Graphs/MinimumSpanningTree/KruskalMstFinder.cs b/MoreStructures/Graphs/MinimumSpanningTree/KruskalMstFinder.cs index c42b7c7e..5c36168e 100644 --- a/MoreStructures/Graphs/MinimumSpanningTree/KruskalMstFinder.cs +++ b/MoreStructures/Graphs/MinimumSpanningTree/KruskalMstFinder.cs @@ -105,7 +105,7 @@ public KruskalMstFinder(IInPlaceSorting sorter, Func disjoint /// /// /// - public ISet<(int, int)> Find(IGraph graph, IDictionary<(int, int), int> distances) + public ISet<(int, int)> Find(IGraph graph, IGraphDistances distances) { var edges = graph.GetAllEdges().ToList(); Sorter.Sort(edges, new EdgesComparer(distances)); @@ -132,9 +132,9 @@ public KruskalMstFinder(IInPlaceSorting sorter, Func disjoint private sealed class EdgesComparer : IComparer<(int, int)> { - private IDictionary<(int, int), int> Distances { get; } + private IGraphDistances Distances { get; } - public EdgesComparer(IDictionary<(int, int), int> distances) + public EdgesComparer(IGraphDistances distances) { Distances = distances; } diff --git a/MoreStructures/Graphs/MinimumSpanningTree/PrimMstFinder.cs b/MoreStructures/Graphs/MinimumSpanningTree/PrimMstFinder.cs index 8522c668..33b3fdaf 100644 --- a/MoreStructures/Graphs/MinimumSpanningTree/PrimMstFinder.cs +++ b/MoreStructures/Graphs/MinimumSpanningTree/PrimMstFinder.cs @@ -80,7 +80,7 @@ public PrimMstFinder(Func> priorityQueueBuilder) /// /// /// - public ISet<(int, int)> Find(IGraph graph, IDictionary<(int, int), int> distances) + public ISet<(int, int)> Find(IGraph graph, IGraphDistances distances) { var numberOfVertices = graph.GetNumberOfVertices(); if (numberOfVertices == 0) diff --git a/MoreStructures/Graphs/ShortestDistance/AStarShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/AStarShortestDistanceFinder.cs new file mode 100644 index 00000000..17d80744 --- /dev/null +++ b/MoreStructures/Graphs/ShortestDistance/AStarShortestDistanceFinder.cs @@ -0,0 +1,78 @@ +using MoreStructures.PriorityQueues; + +namespace MoreStructures.Graphs.ShortestDistance; + +/// +/// A implementation based on the A* algorithm, which is a refinement of +/// the Dijkstra's algorithm, introducing a goal-oriented heuristic, driving the search in the direction of the end +/// vertex. +/// +/// +/// +/// +/// +/// +/// ALGORITHM +///
+/// - The algorithm is described in , with the only difference that +/// edge distances are modified, based on a heuristic defined as a potential function. +///
+/// - New edge distance is defined as follow: given the potential function P, for each edge (u, v) in the graph, +/// d'(u, v) = d(u, v) + P(v) - P(u). +///
+/// - If P is defined correctly, P(u) and P(v) are good approximations of the distance of u and v from the end +/// vertex e. +///
+/// - If so, P(v) - P(u) will be negative if moving from u to v gets us closer to e and positive if it +/// gets us farther from it. +///
+/// - For this reason, given two vertices v' and v'' connected from u via e' = (u, v') and +/// e'' = (u, v''), and such that d(e') = d(e''), if P(v') < P(v'') then +/// d'(e') < d''(e'), so the algorithm will prefer e' over e'' during the exploration, as it seems to +/// be closer to e. +///
+/// - Because the algorithm stops when e is processed, if the algorithm visits e earlier than later, the algorithm +/// will find the shortest path from s to e ealier than later. +///
+/// +/// COMPLEXITY +///
+/// - The complexity heavily depends on the accuracy of the potential function. +///
+/// - A good model of the average performance of the algorithm is very complicated to derive, since the heuristic +/// can drive the exploration much quicker or slower towards the end vertex, depending on how the function is +/// defined. +///
+/// - In general, potential functions which are closer to the actual shortest distance to the end vertex yield +/// better results. The farther they move from the ideal, the less optimized the exploration of the graph +/// becomes. +///
+/// - Worst case remains as in , where all vertices of the graph have +/// to be explored, for a path from the start to the end to be found (or prove there is no path, since start and +/// end lie in two different connected components). +///
+/// - Best case is when P is the shortest distance to e, in which case only the vertices of a shortest path from +/// s to e are visited (which is tipically a small fraction of the vertices of the graph, especially if the graph +/// is large). That is the bare minimum to find the shortest path from s to e. +///
+/// - Average case can even be worse than normal Dijkstra, if P is misleading, i.e. if it drives the exploration +/// away from e, rather than reducing the cost of edges which drives the exploration closer to e. +///
+/// - However, with a well defined P, close enough to the actual shortest distance, Time Complexity is between +/// O(e + v * log(v)) and O(h), where h is the highest number of edges of a shortest path from s to e. +///
+/// - Space Complexity is also between O(h) and O(v). +///
+///
+public class AStarShortestDistanceFinder : PotentialBasedShortestDistanceFinder +{ + /// + /// + /// A builder of a of values, used by the algorithm to + /// store edges with priority from the closest to the start, to the farthest. + /// + public AStarShortestDistanceFinder(Func> priorityQueueBuilder) + : base(new DijkstraShortestDistanceFinder(priorityQueueBuilder)) + { + } +} diff --git a/MoreStructures/Graphs/ShortestDistance/BellmanFordShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/BellmanFordShortestDistanceFinder.cs index c9f55931..853a3020 100644 --- a/MoreStructures/Graphs/ShortestDistance/BellmanFordShortestDistanceFinder.cs +++ b/MoreStructures/Graphs/ShortestDistance/BellmanFordShortestDistanceFinder.cs @@ -2,8 +2,6 @@ namespace MoreStructures.Graphs.ShortestDistance; -using GraphDistances = IDictionary<(int, int), int>; - /// /// An implementation based on the Bellman-Ford algorithm. /// @@ -128,7 +126,7 @@ public BellmanFordShortestDistanceFinder(Func visitStrategyBuild /// /// /// - public (int, IList) Find(IGraph graph, GraphDistances distances, int start, int end) + public (int, IList) Find(IGraph graph, IGraphDistances distances, int start, int end) { ShortestDistanceFinderHelper.ValidateParameters(graph, start, end); diff --git a/MoreStructures/Graphs/ShortestDistance/BfsBasedShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/BfsBasedShortestDistanceFinder.cs index 9a6d2e0e..bed31d3f 100644 --- a/MoreStructures/Graphs/ShortestDistance/BfsBasedShortestDistanceFinder.cs +++ b/MoreStructures/Graphs/ShortestDistance/BfsBasedShortestDistanceFinder.cs @@ -3,8 +3,6 @@ namespace MoreStructures.Graphs.ShortestDistance; -using GraphDistances = IDictionary<(int, int), int>; - /// /// An which runs a BFS on the provided graph from the start vertex, to find the /// shortest distance and a shortest path to the end vertex. @@ -104,7 +102,7 @@ public BfsBasedShortestDistanceFinder(Func visitStrategyBuilder) /// /// /// - public (int, IList) Find(IGraph graph, GraphDistances distances, int start, int end) + public (int, IList) Find(IGraph graph, IGraphDistances distances, int start, int end) { ShortestDistanceFinderHelper.ValidateParameters(graph, start, end); @@ -132,7 +130,7 @@ public BfsBasedShortestDistanceFinder(Func visitStrategyBuilder) private static void UpdateBestPreviousAndDownstreamVertices( object? sender, VisitEventArgs eventArgs, - GraphDistances distances, BestPreviouses bestPreviouses, Dictionary> downstreamVertices) + IGraphDistances distances, BestPreviouses bestPreviouses, Dictionary> downstreamVertices) { var current = eventArgs.Vertex; if (eventArgs.PreviousVertex is not int previous) diff --git a/MoreStructures/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinder.cs new file mode 100644 index 00000000..08cdd57a --- /dev/null +++ b/MoreStructures/Graphs/ShortestDistance/BidirectionalAStarShortestDistanceFinder.cs @@ -0,0 +1,47 @@ +using MoreStructures.PriorityQueues; + +namespace MoreStructures.Graphs.ShortestDistance; + +/// +/// A implementation based on the bidirectional A* algorithm, +/// which combines the goal-oriented heuristic of the A* algorithm and the double search approach of the bidirectional +/// Dijkstra's algorithm. +/// +/// +/// +/// +/// +/// +/// ALGORITHM +///
+/// - Same as , but with the use of the "bidirectional search approach" +/// used in to run Dijkstra's algorithm. +///
+/// +/// COMPLEXITY +///
+/// - The bidirectional search cuts in half, in average, the exploration time of +/// . +///
+/// - That improves average runtime, but doesn't change worst case scenarios and best case scenarios, since in +/// those scenarios the number of vertices to visit is fixed and equal to v and h, respectively, and irrespective +/// of whether the exploration is done unidirectionally or bidirectionally. +///
+/// - Therefore, Time Complexity is O(e + v * log(v)) in the worst case, O(h) in the best case and somewhere in +/// between, still lower than , in all other cases. +///
+/// - Space Complexity is between O(h) and O(v). +///
+///
+public class BidirectionalAStarShortestDistanceFinder : PotentialBasedShortestDistanceFinder +{ + /// + /// + /// A builder of a of values, used by the algorithm to + /// store edges with priority from the closest to the start, to the farthest. + /// + public BidirectionalAStarShortestDistanceFinder(Func> priorityQueueBuilder) + : base(new BidirectionalDijkstraShortestDistanceFinder(priorityQueueBuilder)) + { + } +} diff --git a/MoreStructures/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinder.cs index 16207a38..7998d27b 100644 --- a/MoreStructures/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinder.cs +++ b/MoreStructures/Graphs/ShortestDistance/BidirectionalDijkstraShortestDistanceFinder.cs @@ -2,8 +2,6 @@ namespace MoreStructures.Graphs.ShortestDistance; -using GraphDistances = IDictionary<(int, int), int>; - /// /// A implementation based on a refinement of the Dijkstra algorithm, running /// search in two parallel inversed flows: from the start vertext to the end vertex and viceversa. @@ -161,7 +159,7 @@ public void RunStep() /// /// /// - public (int, IList) Find(IGraph graph, GraphDistances distances, int start, int end) + public (int, IList) Find(IGraph graph, IGraphDistances distances, int start, int end) { ShortestDistanceFinderHelper.ValidateParameters(graph, start, end); diff --git a/MoreStructures/Graphs/ShortestDistance/DijkstraShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/DijkstraShortestDistanceFinder.cs index 92712fbf..81c73d8e 100644 --- a/MoreStructures/Graphs/ShortestDistance/DijkstraShortestDistanceFinder.cs +++ b/MoreStructures/Graphs/ShortestDistance/DijkstraShortestDistanceFinder.cs @@ -3,8 +3,6 @@ namespace MoreStructures.Graphs.ShortestDistance; -using GraphDistances = IDictionary<(int, int), int>; - /// /// A implementation based on the Dijkstra algorithm. /// @@ -119,7 +117,7 @@ public DijkstraShortestDistanceFinder(Func> priorit /// /// /// - public (int, IList) Find(IGraph graph, GraphDistances distances, int start, int end) + public (int, IList) Find(IGraph graph, IGraphDistances distances, int start, int end) { ShortestDistanceFinderHelper.ValidateParameters(graph, start, end); diff --git a/MoreStructures/Graphs/ShortestDistance/IPotentialBasedShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/IPotentialBasedShortestDistanceFinder.cs new file mode 100644 index 00000000..57748173 --- /dev/null +++ b/MoreStructures/Graphs/ShortestDistance/IPotentialBasedShortestDistanceFinder.cs @@ -0,0 +1,63 @@ +namespace MoreStructures.Graphs.ShortestDistance; + +/// +/// A conceptual extension which introduces a potential function +/// heuristic, to better drive the exploration of vertices of the graph, when searching for the end vertex. +/// +/// +/// +/// DEFINITION +///
+/// In order for the potential function to be correct and possibly improve (and not decrease) algorithm +/// performance, the function P should have a few properties. Some are mandatory, some are recommended. +/// listed below. +///
+/// - It must be deterministic, i.e. an actual matematical function, yielding the same output for a +/// given input, every time it is called. +///
+/// - It must be feasible, i.e. it should not make distances negative when applied to edge distances on +/// the graph, if Dijkstra is used as shortest path finder. If distances become negative, Dijkstra +/// cannot be applied, as its main requirement would be broken. +/// Notice how it is not enough for the potential function to be non-negative, since the distance d(u, v) of +/// an edge (u, v) of the graph is shifted by the potential difference, i.e. by P(v) - P(u), which can be +/// negative and can make the overall edge cost, d(u, v) + P(v) - P(u), negative. +///
+/// - It should be quick to calculate the potential, not to have an impact on the overall runtime of the +/// algorithm. Tipically O(1). +///
+/// - It should be minimal at the end vertex, so that Dijkstra would be incentivized to go towards that +/// direction, while exploring the graph. +///
+/// - It should become higher as the distance from the end vertex increases, so that Dijkstra would be +/// disincentivized from getting farther from the end vertex. +///
+/// - It tipically is defined as a lower bound for the shortest distance: e.g. the euclidean distance on +/// the map in a road network. +///
+///
+public interface IPotentialBasedShortestDistanceFinder : IShortestDistanceFinder +{ + /// + /// + /// + /// + /// + /// + /// + /// + /// A function mapping each vertex of the provided graph to a , providing an heuristic for + /// "how far" the vertex is from the end vertex, to drive the + /// towards an earlier, rather than late, discovery of the end vertex. + ///
+ /// Check the general documentation of for further information + /// about how to define a correct and good potential function. + /// + /// + /// + /// + /// + /// + /// + (int, IList) Find( + IGraph graph, IGraphDistances distances, Func potentials, int start, int end); +} diff --git a/MoreStructures/Graphs/ShortestDistance/IShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/IShortestDistanceFinder.cs index ffe2e123..9a16460f 100644 --- a/MoreStructures/Graphs/ShortestDistance/IShortestDistanceFinder.cs +++ b/MoreStructures/Graphs/ShortestDistance/IShortestDistanceFinder.cs @@ -31,5 +31,5 @@ public interface IShortestDistanceFinder /// The second item of the couple is the sequence of vertices of the , identifying the /// shortest path. /// - (int, IList) Find(IGraph graph, IDictionary<(int, int), int> distances, int start, int end); + (int, IList) Find(IGraph graph, IGraphDistances distances, int start, int end); } diff --git a/MoreStructures/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinder.cs b/MoreStructures/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinder.cs new file mode 100644 index 00000000..6ae39314 --- /dev/null +++ b/MoreStructures/Graphs/ShortestDistance/PotentialBasedShortestDistanceFinder.cs @@ -0,0 +1,64 @@ +namespace MoreStructures.Graphs.ShortestDistance; + +/// +/// An implementation, wrapping a +/// algorithm and using a potential function as a heuristic to enhance graph +/// exploration. +/// +/// +/// Common ground for all A* variants, such as and +/// . +///
+/// Check general documentation for the requirements and desired +/// properties for the heuristic. +///
+public abstract class PotentialBasedShortestDistanceFinder : IPotentialBasedShortestDistanceFinder +{ + /// + /// A instance, used to run the shortest distance algorithm on the provided + /// graph. + /// + protected IShortestDistanceFinder Finder { get; } + + /// + /// + /// + /// + protected PotentialBasedShortestDistanceFinder(IShortestDistanceFinder finder) + { + Finder = finder; + } + + /// + public (int, IList) Find(IGraph graph, IGraphDistances distances, int start, int end) => + Find(graph, distances, v => 0, start, end); + + /// + public (int, IList) Find( + IGraph graph, IGraphDistances distances, Func potentials, int start, int end) + { + var alteredDistances = new PotentialFunctionAlteredGraphDistances(distances, potentials); + var (alteredDistance, shortestDistancePath) = Finder.Find(graph, alteredDistances, start, end); + var actualDistance = alteredDistance == int.MaxValue || alteredDistance == int.MinValue + ? alteredDistance + : alteredDistance + potentials(start) - potentials(end); + return (actualDistance, shortestDistancePath); + } + + private sealed class PotentialFunctionAlteredGraphDistances : IGraphDistances + { + private IGraphDistances OriginalDistances { get; } + private Func Potentials { get; } + + public PotentialFunctionAlteredGraphDistances( + IGraphDistances originalDistances, Func potentials) + { + OriginalDistances = originalDistances; + Potentials = potentials; + } + + public int this[(int edgeStart, int edgeEnd) edge] => + OriginalDistances[edge] - Potentials(edge.edgeStart) + Potentials(edge.edgeEnd); + } + +} diff --git a/MoreStructures/Graphs/ShortestDistanceTree/BellmanFordShortestDistanceTreeFinder.cs b/MoreStructures/Graphs/ShortestDistanceTree/BellmanFordShortestDistanceTreeFinder.cs index e8fcdd83..b878ee6a 100644 --- a/MoreStructures/Graphs/ShortestDistanceTree/BellmanFordShortestDistanceTreeFinder.cs +++ b/MoreStructures/Graphs/ShortestDistanceTree/BellmanFordShortestDistanceTreeFinder.cs @@ -3,8 +3,6 @@ namespace MoreStructures.Graphs.ShortestDistanceTree; -using GraphDistances = IDictionary<(int, int), int>; - /// /// A implementation based on the Bellman-Ford algorithm. /// @@ -70,7 +68,7 @@ public BellmanFordShortestDistanceTreeFinder(Func visitStrategyB /// /// /// - public BestPreviouses FindTree(IGraph graph, GraphDistances distances, int start) + public BestPreviouses FindTree(IGraph graph, IGraphDistances distances, int start) { ShortestDistanceFinderHelper.ValidateParameters(graph, start, null); @@ -86,7 +84,7 @@ public BestPreviouses FindTree(IGraph graph, GraphDistances distances, int start } private static void RelaxEdges( - IGraph graph, GraphDistances distances, int numberOfVertices, + IGraph graph, IGraphDistances distances, int numberOfVertices, BestPreviouses bestPreviouses, int iteration, HashSet verticesRelaxedInLastIteration) { for (var source = 0; source < numberOfVertices; source++) diff --git a/MoreStructures/Graphs/ShortestDistanceTree/DijkstraShortestDistanceTreeFinder.cs b/MoreStructures/Graphs/ShortestDistanceTree/DijkstraShortestDistanceTreeFinder.cs index f876d912..e6e47a9a 100644 --- a/MoreStructures/Graphs/ShortestDistanceTree/DijkstraShortestDistanceTreeFinder.cs +++ b/MoreStructures/Graphs/ShortestDistanceTree/DijkstraShortestDistanceTreeFinder.cs @@ -60,7 +60,7 @@ public DijkstraShortestDistanceTreeFinder(Func> pri /// /// /// - public BestPreviouses FindTree(IGraph graph, IDictionary<(int, int), int> distances, int start) + public BestPreviouses FindTree(IGraph graph, IGraphDistances distances, int start) { ShortestDistanceFinderHelper.ValidateParameters(graph, start, null); diff --git a/MoreStructures/Graphs/ShortestDistanceTree/IShortestDistanceTreeFinder.cs b/MoreStructures/Graphs/ShortestDistanceTree/IShortestDistanceTreeFinder.cs index 29b0b5d8..e148e103 100644 --- a/MoreStructures/Graphs/ShortestDistanceTree/IShortestDistanceTreeFinder.cs +++ b/MoreStructures/Graphs/ShortestDistanceTree/IShortestDistanceTreeFinder.cs @@ -50,5 +50,5 @@ public interface IShortestDistanceTreeFinder /// The distance over a path from the root to any node of the tree v is the shortest /// distance from to v, over a directed path of the . /// - BestPreviouses FindTree(IGraph graph, IDictionary<(int, int), int> distances, int start); + BestPreviouses FindTree(IGraph graph, IGraphDistances distances, int start); }