mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-07-07 01:35:16 +08:00
Add tests for AStar.java
, enhance documentation (#5603)
This commit is contained in:
@ -694,6 +694,7 @@
|
|||||||
* dynamicarray
|
* dynamicarray
|
||||||
* [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java)
|
* [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java)
|
||||||
* graphs
|
* 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)
|
* [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)
|
* [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)
|
* [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java)
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
/*
|
|
||||||
Time Complexity = O(E), where E is equal to the number of edges
|
|
||||||
*/
|
|
||||||
package com.thealgorithms.datastructures.graphs;
|
package com.thealgorithms.datastructures.graphs;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.PriorityQueue;
|
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 {
|
public final class AStar {
|
||||||
private AStar() {
|
private AStar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Graph {
|
/**
|
||||||
|
* Represents a graph using an adjacency list.
|
||||||
// Graph's structure can be changed only applying changes to this class.
|
*/
|
||||||
|
static class Graph {
|
||||||
private ArrayList<ArrayList<Edge>> graph;
|
private ArrayList<ArrayList<Edge>> graph;
|
||||||
|
|
||||||
// Initialise ArrayLists in Constructor
|
|
||||||
Graph(int size) {
|
Graph(int size) {
|
||||||
this.graph = new ArrayList<>();
|
this.graph = new ArrayList<>();
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
@ -31,15 +32,17 @@ public final class AStar {
|
|||||||
return this.graph.get(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) {
|
private void addEdge(Edge edge) {
|
||||||
this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight()));
|
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()));
|
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 static class Edge {
|
||||||
|
|
||||||
private int from;
|
private int from;
|
||||||
private int to;
|
private int to;
|
||||||
private int weight;
|
private int weight;
|
||||||
@ -63,12 +66,13 @@ public final class AStar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// class to iterate during the algorithm execution, and also used to return the solution.
|
/**
|
||||||
private static class PathAndDistance {
|
* Contains information about the path and its total distance.
|
||||||
|
*/
|
||||||
private int distance; // distance advanced so far.
|
static class PathAndDistance {
|
||||||
private ArrayList<Integer> path; // list of visited nodes in this path.
|
private int distance; // total distance from the start node
|
||||||
private int estimated; // heuristic value associated to the last node od the path (current 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) {
|
PathAndDistance(int distance, ArrayList<Integer> path, int estimated) {
|
||||||
this.distance = distance;
|
this.distance = distance;
|
||||||
@ -87,112 +91,54 @@ public final class AStar {
|
|||||||
public int getEstimated() {
|
public int getEstimated() {
|
||||||
return estimated;
|
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) {
|
for (int i = 0; i < data.size(); i += 4) {
|
||||||
graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2)));
|
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) {
|
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
|
// PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination
|
||||||
// estimated value
|
|
||||||
// given by the heuristic function to reach the destination point from the current point.
|
|
||||||
PriorityQueue<PathAndDistance> queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated())));
|
PriorityQueue<PathAndDistance> queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated())));
|
||||||
|
|
||||||
// dummy data to start the algorithm from the beginning point
|
// Start with the initial node
|
||||||
queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0));
|
queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from]));
|
||||||
|
|
||||||
boolean solutionFound = false;
|
boolean solutionFound = false;
|
||||||
PathAndDistance currentData = new PathAndDistance(-1, null, -1);
|
PathAndDistance currentData = new PathAndDistance(-1, null, -1);
|
||||||
|
|
||||||
while (!queue.isEmpty() && !solutionFound) {
|
while (!queue.isEmpty() && !solutionFound) {
|
||||||
currentData = queue.poll(); // first in the queue, best node so keep exploring.
|
currentData = queue.poll(); // get the best node from the queue
|
||||||
int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node.
|
int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node
|
||||||
|
|
||||||
|
// Check if the destination has been reached
|
||||||
if (currentPosition == to) {
|
if (currentPosition == to) {
|
||||||
solutionFound = true;
|
solutionFound = true;
|
||||||
} else {
|
} else {
|
||||||
for (Edge edge : graph.getNeighbours(currentPosition)) {
|
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());
|
ArrayList<Integer> updatedPath = new ArrayList<>(currentData.getPath());
|
||||||
updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance,
|
updatedPath.add(edge.getTo());
|
||||||
// and the heuristic function value associated to that path.
|
|
||||||
|
// Update the distance and heuristic for the new path
|
||||||
queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()]));
|
queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1);
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user