package com.jwetherell.algorithms.graph; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.jwetherell.algorithms.data_structures.Graph; import com.jwetherell.algorithms.data_structures.Graph.Edge; import com.jwetherell.algorithms.data_structures.Graph.Vertex; /** * In computer science, A* is a computer algorithm that is widely used in path finding and graph traversal, the process * of plotting an efficiently traversable path between multiple points, called nodes. * * http://en.wikipedia.org/wiki/A*_search_algorithm * * @author Justin Wetherell */ public class AStar> { public AStar() { } /** * Find the path using the A* algorithm from start vertex to end vertex or NULL if no path exists. * * @param graph * Graph to search. * @param start * Start vertex. * @param goal * Goal vertex. * * @return * List of Edges to get from start to end or NULL if no path exists. */ public List> aStar(Graph graph, Graph.Vertex start, Graph.Vertex goal) { final int size = graph.getVertices().size(); // used to size data structures appropriately final Set> closedSet = new HashSet>(size); // The set of nodes already evaluated. final List> openSet = new ArrayList>(size); // The set of tentative nodes to be evaluated, initially containing the start node openSet.add(start); final Map,Graph.Vertex> cameFrom = new HashMap,Graph.Vertex>(size); // The map of navigated nodes. final Map,Integer> gScore = new HashMap,Integer>(); // Cost from start along best known path. gScore.put(start, 0); // Estimated total cost from start to goal through y. final Map,Integer> fScore = new HashMap,Integer>(); for (Graph.Vertex v : graph.getVertices()) fScore.put(v, Integer.MAX_VALUE); fScore.put(start, heuristicCostEstimate(start,goal)); final Comparator> comparator = new Comparator>() { /** * {@inheritDoc} */ @Override public int compare(Vertex o1, Vertex o2) { if (fScore.get(o1) < fScore.get(o2)) return -1; if (fScore.get(o2) < fScore.get(o1)) return 1; return 0; } }; while (!openSet.isEmpty()) { final Graph.Vertex current = openSet.get(0); if (current.equals(goal)) return reconstructPath(cameFrom, goal); openSet.remove(0); closedSet.add(current); for (Graph.Edge edge : current.getEdges()) { final Graph.Vertex neighbor = edge.getToVertex(); if (closedSet.contains(neighbor)) continue; // Ignore the neighbor which is already evaluated. final int tenativeGScore = gScore.get(current) + distanceBetween(current,neighbor); // length of this path. if (!openSet.contains(neighbor)) openSet.add(neighbor); // Discover a new node else if (tenativeGScore >= gScore.get(neighbor)) continue; // This path is the best until now. Record it! cameFrom.put(neighbor, current); gScore.put(neighbor, tenativeGScore); final int estimatedFScore = gScore.get(neighbor) + heuristicCostEstimate(neighbor, goal); fScore.put(neighbor, estimatedFScore); // fScore has changed, re-sort the list Collections.sort(openSet,comparator); } } return null; } /** * Default distance is the edge cost. If there is no edge between the start and next then * it returns Integer.MAX_VALUE; */ protected int distanceBetween(Graph.Vertex start, Graph.Vertex next) { for (Edge e : start.getEdges()) { if (e.getToVertex().equals(next)) return e.getCost(); } return Integer.MAX_VALUE; } /** * Default heuristic: cost to each vertex is 1. */ protected int heuristicCostEstimate(Graph.Vertex start, Graph.Vertex goal) { return 1; } private List> reconstructPath(Map,Graph.Vertex> cameFrom, Graph.Vertex current) { final List> totalPath = new ArrayList>(); while (current != null) { final Graph.Vertex previous = current; current = cameFrom.get(current); if (current != null) { final Graph.Edge edge = current.getEdge(previous); totalPath.add(edge); } } Collections.reverse(totalPath); return totalPath; } }