From f0cda94c3373ffcf8f196bb3ec5f31f18bfe1556 Mon Sep 17 00:00:00 2001 From: surajdm123 Date: Mon, 13 Apr 2026 00:03:15 -0700 Subject: [PATCH 1/2] Add A* Search Algorithm for Weighted Graph Pathfinding --- .../com/thealgorithms/others/AStarSearch.java | 145 ++++++++++++++++++ .../thealgorithms/others/AStarSearchTest.java | 125 +++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 src/main/java/com/thealgorithms/others/AStarSearch.java create mode 100644 src/test/java/com/thealgorithms/others/AStarSearchTest.java diff --git a/src/main/java/com/thealgorithms/others/AStarSearch.java b/src/main/java/com/thealgorithms/others/AStarSearch.java new file mode 100644 index 000000000000..9894a61958a9 --- /dev/null +++ b/src/main/java/com/thealgorithms/others/AStarSearch.java @@ -0,0 +1,145 @@ +package com.thealgorithms.others; + +import java.util.*; + +/** + * A* (A-Star) Search Algorithm implementation for finding the shortest path + * between two nodes in a graph. + * + *

This algorithm uses a heuristic to guide its search, making it more efficient + * than Dijkstra’s algorithm in many cases. + * + *

f(n) = g(n) + h(n) + * where: + * - g(n): cost from start node to current node + * - h(n): estimated cost from current node to goal (heuristic) + * + *

Time Complexity: + * - Worst case: O(E log V) + * + *

Space Complexity: + * - O(V) + * + *

Use Cases: + * - Pathfinding (maps, games) + * - AI planning + * + * @author Suraj Devatha + */ +public class AStarSearch { + + /** + * Finds shortest path using A*. + * + * @param graph adjacency list + * @param start start node + * @param goal goal node + * @param heuristic heuristic function + * @return list of nodes representing shortest path + */ + public static List findPath( + Map> graph, + Node start, + Node goal, + Heuristic heuristic + ) { + + Map gScore = new HashMap<>(); + Map fScore = new HashMap<>(); + Map cameFrom = new HashMap<>(); + + PriorityQueue openSet = new PriorityQueue<>( + Comparator.comparingDouble(fScore::get) + ); + + gScore.put(start, 0.0); + fScore.put(start, heuristic.estimate(start, goal)); + + openSet.add(start); + + while (!openSet.isEmpty()) { + Node current = openSet.poll(); + + if (current.equals(goal)) { + return reconstructPath(cameFrom, current); + } + + for (Edge edge : graph.getOrDefault(current, Collections.emptyList())) { + Node neighbor = edge.target; + double tentativeG = gScore.get(current) + edge.cost; + + if (tentativeG < gScore.getOrDefault(neighbor, Double.POSITIVE_INFINITY)) { + cameFrom.put(neighbor, current); + gScore.put(neighbor, tentativeG); + fScore.put(neighbor, tentativeG + heuristic.estimate(neighbor, goal)); + + if (!openSet.contains(neighbor)) { + openSet.add(neighbor); + } + } + } + } + + return Collections.emptyList(); // No path found + } + + /** + * Reconstructs path from goal to start. + */ + private static List reconstructPath(Map cameFrom, Node current) { + List path = new ArrayList<>(); + path.add(current); + + while (cameFrom.containsKey(current)) { + current = cameFrom.get(current); + path.add(current); + } + + Collections.reverse(path); + return path; + } + + /** + * Heuristic interface (can plug different heuristics). + */ + public interface Heuristic { + double estimate(Node current, Node goal); + } + + /** + * Node class representing a vertex in the graph. + */ + static class Node { + String id; + + Node(String id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Node)) return false; + Node node = (Node) o; + return Objects.equals(id, node.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + } + + /** + * Edge class representing weighted connections. + */ + static class Edge { + Node target; + double cost; + + Edge(Node target, double cost) { + this.target = target; + this.cost = cost; + } + } +} diff --git a/src/test/java/com/thealgorithms/others/AStarSearchTest.java b/src/test/java/com/thealgorithms/others/AStarSearchTest.java new file mode 100644 index 000000000000..43248d87af2b --- /dev/null +++ b/src/test/java/com/thealgorithms/others/AStarSearchTest.java @@ -0,0 +1,125 @@ +package com.thealgorithms.others; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test cases for AStarSearch algorithm. + */ +class AStarSearchTest { + + /** + * Simple heuristic that returns 0 (reduces A* to Dijkstra). + */ + private static final AStarSearch.Heuristic ZERO_HEURISTIC = (node, goal) -> 0; + private static Map> graph; + private static AStarSearch.Node A; + private static AStarSearch.Node B; + private static AStarSearch.Node C; + private static AStarSearch.Node D; + + @BeforeAll + static void setUp() { + graph = new HashMap<>(); + + A = new AStarSearch.Node("A"); + B = new AStarSearch.Node("B"); + C = new AStarSearch.Node("C"); + D = new AStarSearch.Node("D"); + + graph.put(A, Arrays.asList( + new AStarSearch.Edge(B, 1), + new AStarSearch.Edge(C, 4) + )); + + graph.put(B, Arrays.asList( + new AStarSearch.Edge(C, 2), + new AStarSearch.Edge(D, 5) + )); + + graph.put(C, Arrays.asList( + new AStarSearch.Edge(D, 1) + )); + + graph.put(D, Collections.emptyList()); + } + + @Test + void testPathExists() { + List path = + AStarSearch.findPath(graph, A, D, ZERO_HEURISTIC); + + // Expected shortest path: A -> B -> C -> D + List expected = Arrays.asList(A, B, C, D); + + assertEquals(expected, path); + } + + @Test + void testDirectPath() { + List path = + AStarSearch.findPath(graph, A, B, ZERO_HEURISTIC); + + List expected = Arrays.asList(A, B); + + assertEquals(expected, path); + } + + @Test + void testStartEqualsGoal() { + List path = + AStarSearch.findPath(graph, A, A, ZERO_HEURISTIC); + + List expected = Collections.singletonList(A); + + assertEquals(expected, path); + } + + @Test + void testNoPathExists() { + AStarSearch.Node E = new AStarSearch.Node("E"); + + List path = + AStarSearch.findPath(graph, E, A, ZERO_HEURISTIC); + + assertTrue(path.isEmpty()); + } + + @Test + void testPathCostOptimality() { + List path = + AStarSearch.findPath(graph, A, D, ZERO_HEURISTIC); + + // Calculate total cost + double cost = calculatePathCost(path); + + // Expected shortest cost = 1 (A->B) + 2 (B->C) + 1 (C->D) = 4 + assertEquals(4.0, cost); + } + + /** + * Utility method to calculate path cost. + */ + private double calculatePathCost(List path) { + double total = 0; + + for (int i = 0; i < path.size() - 1; i++) { + AStarSearch.Node current = path.get(i); + AStarSearch.Node next = path.get(i + 1); + + for (AStarSearch.Edge edge : graph.getOrDefault(current, Collections.emptyList())) { + if (edge.target.equals(next)) { + total += edge.cost; + break; + } + } + } + + return total; + } +} From 18a2009d95b6e4359a180e48970210e9289d22b3 Mon Sep 17 00:00:00 2001 From: surajdm123 Date: Mon, 13 Apr 2026 00:03:15 -0700 Subject: [PATCH 2/2] Add A* Search Algorithm for Weighted Graph Pathfinding --- .../com/thealgorithms/others/AStarSearch.java | 33 +++++---- .../thealgorithms/others/AStarSearchTest.java | 69 ++++++++----------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/thealgorithms/others/AStarSearch.java b/src/main/java/com/thealgorithms/others/AStarSearch.java index 9894a61958a9..013d597657f2 100644 --- a/src/main/java/com/thealgorithms/others/AStarSearch.java +++ b/src/main/java/com/thealgorithms/others/AStarSearch.java @@ -1,6 +1,13 @@ package com.thealgorithms.others; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.PriorityQueue; /** * A* (A-Star) Search Algorithm implementation for finding the shortest path @@ -26,7 +33,10 @@ * * @author Suraj Devatha */ -public class AStarSearch { +public final class AStarSearch { + + private AStarSearch() { + } /** * Finds shortest path using A*. @@ -37,20 +47,13 @@ public class AStarSearch { * @param heuristic heuristic function * @return list of nodes representing shortest path */ - public static List findPath( - Map> graph, - Node start, - Node goal, - Heuristic heuristic - ) { + public static List findPath(Map> graph, Node start, Node goal, Heuristic heuristic) { Map gScore = new HashMap<>(); Map fScore = new HashMap<>(); Map cameFrom = new HashMap<>(); - PriorityQueue openSet = new PriorityQueue<>( - Comparator.comparingDouble(fScore::get) - ); + PriorityQueue openSet = new PriorityQueue<>(Comparator.comparingDouble(fScore::get)); gScore.put(start, 0.0); fScore.put(start, heuristic.estimate(start, goal)); @@ -118,8 +121,12 @@ static class Node { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Node)) return false; + if (this == o) { + return true; + } + if (!(o instanceof Node)) { + return false; + } Node node = (Node) o; return Objects.equals(id, node.id); } diff --git a/src/test/java/com/thealgorithms/others/AStarSearchTest.java b/src/test/java/com/thealgorithms/others/AStarSearchTest.java index 43248d87af2b..62debd27a165 100644 --- a/src/test/java/com/thealgorithms/others/AStarSearchTest.java +++ b/src/test/java/com/thealgorithms/others/AStarSearchTest.java @@ -1,13 +1,16 @@ package com.thealgorithms.others; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import java.util.*; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + /** * Test cases for AStarSearch algorithm. */ @@ -18,82 +21,70 @@ class AStarSearchTest { */ private static final AStarSearch.Heuristic ZERO_HEURISTIC = (node, goal) -> 0; private static Map> graph; - private static AStarSearch.Node A; - private static AStarSearch.Node B; - private static AStarSearch.Node C; - private static AStarSearch.Node D; + + private static AStarSearch.Node nodeA; + private static AStarSearch.Node nodeB; + private static AStarSearch.Node nodeC; + private static AStarSearch.Node nodeD; @BeforeAll static void setUp() { graph = new HashMap<>(); - A = new AStarSearch.Node("A"); - B = new AStarSearch.Node("B"); - C = new AStarSearch.Node("C"); - D = new AStarSearch.Node("D"); + nodeA = new AStarSearch.Node("A"); + nodeB = new AStarSearch.Node("B"); + nodeC = new AStarSearch.Node("C"); + nodeD = new AStarSearch.Node("D"); - graph.put(A, Arrays.asList( - new AStarSearch.Edge(B, 1), - new AStarSearch.Edge(C, 4) - )); + graph.put(nodeA, Arrays.asList(new AStarSearch.Edge(nodeB, 1), new AStarSearch.Edge(nodeC, 4))); - graph.put(B, Arrays.asList( - new AStarSearch.Edge(C, 2), - new AStarSearch.Edge(D, 5) - )); + graph.put(nodeB, Arrays.asList(new AStarSearch.Edge(nodeC, 2), new AStarSearch.Edge(nodeD, 5))); - graph.put(C, Arrays.asList( - new AStarSearch.Edge(D, 1) - )); + graph.put(nodeC, Arrays.asList(new AStarSearch.Edge(nodeD, 1))); - graph.put(D, Collections.emptyList()); + graph.put(nodeD, Collections.emptyList()); } @Test void testPathExists() { - List path = - AStarSearch.findPath(graph, A, D, ZERO_HEURISTIC); + List path = AStarSearch.findPath(graph, nodeA, nodeD, ZERO_HEURISTIC); // Expected shortest path: A -> B -> C -> D - List expected = Arrays.asList(A, B, C, D); + List expected = Arrays.asList(nodeA, nodeB, nodeC, nodeD); assertEquals(expected, path); } @Test void testDirectPath() { - List path = - AStarSearch.findPath(graph, A, B, ZERO_HEURISTIC); + List path = AStarSearch.findPath(graph, nodeA, nodeB, ZERO_HEURISTIC); - List expected = Arrays.asList(A, B); + List expected = Arrays.asList(nodeA, nodeB); assertEquals(expected, path); } @Test void testStartEqualsGoal() { - List path = - AStarSearch.findPath(graph, A, A, ZERO_HEURISTIC); + List path = AStarSearch.findPath(graph, nodeA, nodeA, ZERO_HEURISTIC); - List expected = Collections.singletonList(A); + List expected = Collections.singletonList(nodeA); assertEquals(expected, path); } @Test void testNoPathExists() { - AStarSearch.Node E = new AStarSearch.Node("E"); + AStarSearch.Node nodeE = new AStarSearch.Node("nodeE"); - List path = - AStarSearch.findPath(graph, E, A, ZERO_HEURISTIC); + List path = AStarSearch.findPath(graph, nodeE, nodeA, ZERO_HEURISTIC); assertTrue(path.isEmpty()); } @Test void testPathCostOptimality() { - List path = - AStarSearch.findPath(graph, A, D, ZERO_HEURISTIC); + List path = AStarSearch.findPath(graph, nodeA, nodeD, ZERO_HEURISTIC); // Calculate total cost double cost = calculatePathCost(path);