Skip to content

Add tests for AStar.java, enhance documentation #5603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
144 changes: 45 additions & 99 deletions src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
Original file line number Diff line number Diff line change
@@ -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<ArrayList<Edge>> graph;

// Initialise ArrayLists in Constructor
Graph(int size) {
this.graph = new ArrayList<>();
for (int i = 0; i < size; i++) {
Expand All @@ -31,15 +32,17 @@ private ArrayList<Edge> 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;
Expand All @@ -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<Integer> 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<Integer> path; // list of nodes in the path
private int estimated; // heuristic estimate for reaching the destination

PathAndDistance(int distance, ArrayList<Integer> path, int estimated) {
this.distance = distance;
Expand All @@ -87,112 +91,54 @@ public ArrayList<Integer> 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<Integer> data) {
// Initializes the graph with edges defined in the input data
static void initializeGraph(Graph graph, ArrayList<Integer> 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<Integer> 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<PathAndDistance> 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<Integer> 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
}
}
Original file line number Diff line number Diff line change
@@ -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<Integer> 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");
}
}