All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.shortestpath.AStarShortestPath Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2015-2023, by Joris Kinable, Jon Robison, Thomas Breitbart and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.alg.shortestpath;

import org.jgrapht.*;
import org.jgrapht.alg.interfaces.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.graph.*;
import org.jheaps.*;
import org.jheaps.tree.*;

import java.util.*;
import java.util.function.*;

/**
 * A* shortest path.
 * 

* An implementation of A* shortest path * algorithm. This class works for directed and undirected graphs, as well as multi-graphs and * mixed-graphs. The graph can also change between invocations of the * {@link #getPath(Object, Object)} method; no new instance of this class has to be created. The * heuristic is implemented using a PairingHeap data structure by default to maintain the set of * open nodes. However, there still exist several approaches in literature to improve the * performance of this heuristic which one could consider to implement. Custom heap implementation * can be specified during the construction time. Another issue to take into consideration is the * following: given two candidate nodes, $i$, $j$ to expand, where $f(i)=f(j)$, $g(i)$ > $g(j)$, * $h(i)$ < $g(j)$, $f(i)=g(i)+h(i)$, $g(i)$ is the actual distance from the source node to $i$, * $h(i)$ is the estimated distance from $i$ to the target node. Usually a depth-first search is * desired, so ideally we would expand node $i$ first. Using the PairingHeap, this is not * necessarily the case though. This could be improved in a later version. * *

* Note: This implementation works with both consistent and inconsistent admissible heuristics. For * details on consistency, refer to the description of the method * {@link AStarAdmissibleHeuristic#isConsistent(Graph)}. However, this class is not optimized * for inconsistent heuristics. Several opportunities to improve both worst case and average runtime * complexities for A* with inconsistent heuristics described in literature can be used to improve * this implementation! * * @param the graph vertex type * @param the graph edge type * @author Joris Kinable * @author Jon Robison * @author Thomas Breitbart */ public class AStarShortestPath extends BaseShortestPathAlgorithm { // Supplier of the preferable heap implementation protected final Supplier> heapSupplier; // List of open nodes protected AddressableHeap openList; protected Map> vertexToHeapNodeMap; // List of closed nodes protected Set closedList; // Mapping of nodes to their g-scores (g(x)). protected Map gScoreMap; // Predecessor map: mapping of a node to an edge that leads to its // predecessor on its shortest path towards the targetVertex protected Map cameFrom; // Reference to the admissible heuristic protected AStarAdmissibleHeuristic admissibleHeuristic; // Counter which keeps track of the number of expanded nodes protected int numberOfExpandedNodes; // Comparator for comparing doubles with tolerance protected Comparator comparator; /** * Create a new instance of the A* shortest path algorithm. * * @param graph the input graph * @param admissibleHeuristic admissible heuristic which estimates the distance from a node to * the target node. The heuristic must never overestimate the distance. */ public AStarShortestPath(Graph graph, AStarAdmissibleHeuristic admissibleHeuristic) { this(graph, admissibleHeuristic, PairingHeap::new); } /** * Create a new instance of the A* shortest path algorithm. * * @param graph the input graph * @param admissibleHeuristic admissible heuristic which estimates the distance from a node to * the target node. The heuristic must never overestimate the distance. * @param heapSupplier supplier of the preferable heap implementation */ public AStarShortestPath( Graph graph, AStarAdmissibleHeuristic admissibleHeuristic, Supplier> heapSupplier) { super(graph); this.admissibleHeuristic = Objects.requireNonNull(admissibleHeuristic, "Heuristic function cannot be null!"); this.comparator = new ToleranceDoubleComparator(); this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null!"); } /** * Initializes the data structures. * * @param admissibleHeuristic admissible heuristic */ private void initialize(AStarAdmissibleHeuristic admissibleHeuristic) { this.admissibleHeuristic = admissibleHeuristic; openList = heapSupplier.get(); vertexToHeapNodeMap = new HashMap<>(); closedList = new HashSet<>(); gScoreMap = new HashMap<>(); cameFrom = new HashMap<>(); numberOfExpandedNodes = 0; } /** * Calculates (and returns) the shortest path from the sourceVertex to the targetVertex. Note: * each time you invoke this method, the path gets recomputed. * * @param sourceVertex source vertex * @param targetVertex target vertex * @return the shortest path from sourceVertex to targetVertex */ @Override public GraphPath getPath(V sourceVertex, V targetVertex) { if (!graph.containsVertex(sourceVertex) || !graph.containsVertex(targetVertex)) { throw new IllegalArgumentException( "Source or target vertex not contained in the graph!"); } if (sourceVertex.equals(targetVertex)) { return createEmptyPath(sourceVertex, targetVertex); } this.initialize(admissibleHeuristic); gScoreMap.put(sourceVertex, 0.0); AddressableHeap.Handle heapNode = openList.insert(0.0, sourceVertex); vertexToHeapNodeMap.put(sourceVertex, heapNode); do { AddressableHeap.Handle currentNode = openList.deleteMin(); // Check whether we reached the target vertex if (currentNode.getValue().equals(targetVertex)) { // Build the path return this.buildGraphPath(sourceVertex, targetVertex, currentNode.getKey()); } // We haven't reached the target vertex yet; expand the node expandNode(currentNode, targetVertex); closedList.add(currentNode.getValue()); } while (!openList.isEmpty()); // No path exists from sourceVertex to TargetVertex return createEmptyPath(sourceVertex, targetVertex); } /** * Returns how many nodes have been expanded in the A* search procedure in its last invocation. * A node is expanded if it is removed from the open list. * * @return number of expanded nodes */ public int getNumberOfExpandedNodes() { return numberOfExpandedNodes; } private void expandNode(AddressableHeap.Handle currentNode, V endVertex) { numberOfExpandedNodes++; Set outgoingEdges = graph.outgoingEdgesOf(currentNode.getValue()); for (E edge : outgoingEdges) { V successor = Graphs.getOppositeVertex(graph, edge, currentNode.getValue()); if (successor.equals(currentNode.getValue())) { // Ignore self-loop continue; } double gScore = gScoreMap.get(currentNode.getValue()); double tentativeGScore = gScore + graph.getEdgeWeight(edge); double fScore = tentativeGScore + admissibleHeuristic.getCostEstimate(successor, endVertex); if (vertexToHeapNodeMap.containsKey(successor)) { // We re-encountered a vertex. It's // either in the open or closed list. if (tentativeGScore >= gScoreMap.get(successor)) // Ignore path since it is // non-improving continue; cameFrom.put(successor, edge); gScoreMap.put(successor, tentativeGScore); if (closedList.contains(successor)) { // it's in the closed list. Move node back to // open list, since we discovered a shorter // path to this node closedList.remove(successor); openList.insert(fScore, vertexToHeapNodeMap.get(successor).getValue()); } else { // It's in the open list vertexToHeapNodeMap.get(successor).decreaseKey(fScore); } } else { // We've encountered a new vertex. cameFrom.put(successor, edge); gScoreMap.put(successor, tentativeGScore); AddressableHeap.Handle heapNode = openList.insert(fScore, successor); vertexToHeapNodeMap.put(successor, heapNode); } } } /** * Builds the graph path * * @param startVertex starting vertex of the path * @param targetVertex ending vertex of the path * @param pathLength length of the path * @return the shortest path from startVertex to endVertex */ private GraphPath buildGraphPath(V startVertex, V targetVertex, double pathLength) { List edgeList = new ArrayList<>(); List vertexList = new ArrayList<>(); vertexList.add(targetVertex); V v = targetVertex; while (!v.equals(startVertex)) { edgeList.add(cameFrom.get(v)); v = Graphs.getOppositeVertex(graph, cameFrom.get(v), v); vertexList.add(v); } Collections.reverse(edgeList); Collections.reverse(vertexList); return new GraphWalk<>(graph, startVertex, targetVertex, vertexList, edgeList, pathLength); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy