diff --git a/DIRECTORY.md b/DIRECTORY.md index 7e5d0d3de9ba..87922528abda 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -694,6 +694,7 @@ * dynamicarray * [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java) * graphs + * [AStarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java) * [BipartiteGraphDFSTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java) * [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java) * [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java) diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java index 54fb5fba5c1b..460c05e04403 100644 --- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java +++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java @@ -1,25 +1,26 @@ -/* - Time Complexity = O(E), where E is equal to the number of edges - */ package com.thealgorithms.datastructures.graphs; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.PriorityQueue; +/** + * AStar class implements the A* pathfinding algorithm to find the shortest path in a graph. + * The graph is represented using an adjacency list, and the algorithm uses a heuristic to estimate + * the cost to reach the destination node. + * Time Complexity = O(E), where E is equal to the number of edges + */ public final class AStar { private AStar() { } - private static class Graph { - - // Graph's structure can be changed only applying changes to this class. - + /** + * Represents a graph using an adjacency list. + */ + static class Graph { private ArrayList> graph; - // Initialise ArrayLists in Constructor Graph(int size) { this.graph = new ArrayList<>(); for (int i = 0; i < size; i++) { @@ -31,15 +32,17 @@ private ArrayList getNeighbours(int from) { return this.graph.get(from); } - // Graph is bidirectional, for just one direction remove second instruction of this method. + // Add a bidirectional edge to the graph private void addEdge(Edge edge) { this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight())); this.graph.get(edge.getTo()).add(new Edge(edge.getTo(), edge.getFrom(), edge.getWeight())); } } + /** + * Represents an edge in the graph with a start node, end node, and weight. + */ private static class Edge { - private int from; private int to; private int weight; @@ -63,12 +66,13 @@ public int getWeight() { } } - // class to iterate during the algorithm execution, and also used to return the solution. - private static class PathAndDistance { - - private int distance; // distance advanced so far. - private ArrayList path; // list of visited nodes in this path. - private int estimated; // heuristic value associated to the last node od the path (current node). + /** + * Contains information about the path and its total distance. + */ + static class PathAndDistance { + private int distance; // total distance from the start node + private ArrayList path; // list of nodes in the path + private int estimated; // heuristic estimate for reaching the destination PathAndDistance(int distance, ArrayList path, int estimated) { this.distance = distance; @@ -87,112 +91,54 @@ public ArrayList getPath() { public int getEstimated() { return estimated; } - - private void printSolution() { - if (this.path != null) { - System.out.println("Optimal path: " + this.path + ", distance: " + this.distance); - } else { - System.out.println("There is no path available to connect the points"); - } - } } - private static void initializeGraph(Graph graph, ArrayList data) { + // Initializes the graph with edges defined in the input data + static void initializeGraph(Graph graph, ArrayList data) { for (int i = 0; i < data.size(); i += 4) { graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2))); } - /* - .x. node - (y) cost - - or | or / bidirectional connection - - ( 98)- .7. -(86)- .4. - | - ( 85)- .17. -(142)- .18. -(92)- .8. -(87)- .11. - | - . 1. -------------------- (160) - | \ | - (211) \ .6. - | \ | - . 5. (101)-.13. -(138) (115) - | | | / - ( 99) ( 97) | / - | | | / - .12. -(151)- .15. -(80)- .14. | / - | | | | / - ( 71) (140) (146)- .2. -(120) - | | | - .19. -( 75)- . 0. .10. -(75)- .3. - | | - (118) ( 70) - | | - .16. -(111)- .9. - */ - } - - public static void main(String[] args) { - // heuristic function optimistic values - int[] heuristic = { - 366, - 0, - 160, - 242, - 161, - 178, - 77, - 151, - 226, - 244, - 241, - 234, - 380, - 98, - 193, - 253, - 329, - 80, - 199, - 374, - }; - - Graph graph = new Graph(20); - ArrayList graphData = new ArrayList<>(Arrays.asList(0, 19, 75, null, 0, 15, 140, null, 0, 16, 118, null, 19, 12, 71, null, 12, 15, 151, null, 16, 9, 111, null, 9, 10, 70, null, 10, 3, 75, null, 3, 2, 120, null, 2, 14, 146, null, 2, 13, 138, null, 2, 6, 115, null, 15, 14, 80, null, - 15, 5, 99, null, 14, 13, 97, null, 5, 1, 211, null, 13, 1, 101, null, 6, 1, 160, null, 1, 17, 85, null, 17, 7, 98, null, 7, 4, 86, null, 17, 18, 142, null, 18, 8, 92, null, 8, 11, 87)); - initializeGraph(graph, graphData); - - PathAndDistance solution = aStar(3, 1, graph, heuristic); - solution.printSolution(); } + /** + * Implements the A* pathfinding algorithm to find the shortest path from a start node to a destination node. + * + * @param from the starting node + * @param to the destination node + * @param graph the graph representation of the problem + * @param heuristic the heuristic estimates for each node + * @return a PathAndDistance object containing the shortest path and its distance + */ public static PathAndDistance aStar(int from, int to, Graph graph, int[] heuristic) { - // nodes are prioritised by the less value of the current distance of their paths, and the - // estimated value - // given by the heuristic function to reach the destination point from the current point. + // PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination PriorityQueue queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated()))); - // dummy data to start the algorithm from the beginning point - queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0)); + // Start with the initial node + queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from])); boolean solutionFound = false; PathAndDistance currentData = new PathAndDistance(-1, null, -1); + while (!queue.isEmpty() && !solutionFound) { - currentData = queue.poll(); // first in the queue, best node so keep exploring. - int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node. + currentData = queue.poll(); // get the best node from the queue + int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node + + // Check if the destination has been reached if (currentPosition == to) { solutionFound = true; } else { for (Edge edge : graph.getNeighbours(currentPosition)) { - if (!currentData.getPath().contains(edge.getTo())) { // Avoid Cycles + // Avoid cycles by checking if the next node is already in the path + if (!currentData.getPath().contains(edge.getTo())) { ArrayList updatedPath = new ArrayList<>(currentData.getPath()); - updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance, - // and the heuristic function value associated to that path. + updatedPath.add(edge.getTo()); + + // Update the distance and heuristic for the new path queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()])); } } } } return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1); - // Out of while loop, if there is a solution, the current Data stores the optimal path, and - // its distance } } diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java new file mode 100644 index 000000000000..dce5a6ed4b69 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java @@ -0,0 +1,46 @@ +package com.thealgorithms.datastructures.graphs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AStarTest { + + private AStar.Graph graph; + private int[] heuristic; + + @BeforeEach + public void setUp() { + // Initialize graph and heuristic values for testing + graph = new AStar.Graph(5); + ArrayList graphData = new ArrayList<>(Arrays.asList(0, 1, 1, null, 0, 2, 2, null, 1, 3, 1, null, 2, 3, 1, null, 3, 4, 1, null)); + AStar.initializeGraph(graph, graphData); + + heuristic = new int[] {5, 4, 3, 2, 0}; // Heuristic values for each node + } + + @Test + public void testAStarFindsPath() { + AStar.PathAndDistance result = AStar.aStar(0, 4, graph, heuristic); + assertEquals(3, result.getDistance(), "Expected distance from 0 to 4 is 3"); + assertEquals(Arrays.asList(0, 1, 3, 4), result.getPath(), "Expected path from 0 to 4"); + } + + @Test + public void testAStarPathNotFound() { + AStar.PathAndDistance result = AStar.aStar(0, 5, graph, heuristic); // Node 5 does not exist + assertEquals(-1, result.getDistance(), "Expected distance when path not found is -1"); + assertNull(result.getPath(), "Expected path should be null when no path exists"); + } + + @Test + public void testAStarSameNode() { + AStar.PathAndDistance result = AStar.aStar(0, 0, graph, heuristic); + assertEquals(0, result.getDistance(), "Expected distance from 0 to 0 is 0"); + assertEquals(Arrays.asList(0), result.getPath(), "Expected path should only contain the start node"); + } +}