A* search algorithm
A* is a graph traversal and path search algorithm, which is often used in many fields of computer science due to its completeness, optimality, and optimal efficiency. One major practical drawback is its space complexity, as it stores all generated nodes in memory. Thus, in practical travel-routing systems, it is generally outperformed by algorithms which can pre-process the graph to attain better performance, as well as memory-bounded approaches; however, A* is still the best solution in many cases.
Peter Hart, Nils Nilsson and Bertram Raphael of Stanford Research Institute first published the algorithm in 1968. It can be seen as an extension of Edsger Dijkstra's 1959 algorithm. A* achieves better performance by using heuristics to guide its search.
History
A* was created as part of the Shakey project, which had the aim of building a mobile robot that could plan its own actions. Nils Nilsson originally proposed using the Graph Traverser algorithm for Shakey's path planning. Graph Traverser is guided by a heuristic function the estimated distance from node to the goal node: it entirely ignores the distance from the start node to Bertram Raphael suggested using the sum,. Peter Hart invented the concepts we now call admissibility and consistency of heuristic functions. A* was originally designed for finding least-cost paths when the cost of a path is the sum of its edge costs, but it has been shown that A* can be used to find optimal paths for any problem satisfying the conditions of a cost algebra.The original 1968 A* paper contained a theorem that no A*-like algorithm could expand fewer nodes than A* if the heuristic function is consistent and A*’s tie-breaking rule is suitably chosen. A ″correction″ was published a few years later claiming that consistency was not required, but this was shown to be false in Dechter and Pearl's definitive study of A*'s optimality, which gave an example of A* with a heuristic that was admissible but not consistent expanding arbitrarily more nodes than an alternative A*-like algorithm.
Description
A* is an informed search algorithm, or a best-first search, meaning that it is formulated in terms of weighted graphs: starting from a specific starting node of a graph, it aims to find a path to the given goal node having the smallest cost. It does this by maintaining a tree of paths originating at the start node and extending those paths one edge at a time until its termination criterion is satisfied.At each iteration of its main loop, A* needs to determine which of its paths to extend. It does so based on the cost of the path and an estimate of the cost required to extend the path all the way to the goal. Specifically, A* selects the path that minimizes
where is the next node on the path, is the cost of the path from the start node to, and is a heuristic function that estimates the cost of the cheapest path from to the goal. A* terminates when the path it chooses to extend is a path from start to goal or if there are no paths eligible to be extended. The heuristic function is problem-specific. If the heuristic function is admissible, meaning that it never overestimates the actual cost to get to the goal, A* is guaranteed to return a least-cost path from start to goal.
Typical implementations of A* use a priority queue to perform the repeated selection of minimum cost nodes to expand. This priority queue is known as the open set or fringe. At each step of the algorithm, the node with the lowest value is removed from the queue, the and values of its neighbors are updated accordingly, and these neighbors are added to the queue. The algorithm continues until a goal node has a lower value than any node in the queue. The value of the goal is then the cost of the shortest path, since at the goal is zero in an admissible heuristic.
The algorithm described so far gives us only the length of the shortest path. To find the actual sequence of steps, the algorithm can be easily revised so that each node on the path keeps track of its predecessor. After this algorithm is run, the ending node will point to its predecessor, and so on, until some node's predecessor is the start node.
As an example, when searching for the shortest route on a map, might represent the straight-line distance to the goal, since that is physically the smallest possible distance between any two points. For a grid map from a video game, using the Manhattan distance or the octile distance becomes better depending on the set of movements available.
If the heuristic satisfies the additional condition for every edge of the graph, then is called monotone, or consistent. With a consistent heuristic, A* is guaranteed to find an optimal path without processing any node more than once and A* is equivalent to running Dijkstra's algorithm with the reduced cost.
Pseudocode
The following pseudocode describes the algorithm:function reconstruct_path
total_path :=
while current in cameFrom.Keys:
current := cameFrom
total_path.prepend
return total_path
// A* finds a path from start to goal.
// h is the heuristic function. h estimates the cost to reach goal from node n.
function A_Star
// The set of discovered nodes that may need to be expanded.
// Initially, only the start node is known.
// This is usually implemented as a min-heap or priority queue rather than a hash-set.
openSet :=
// For node n, cameFrom is the node immediately preceding it on the cheapest path from start
// to n currently known.
cameFrom := an empty map
// For node n, gScore is the cost of the cheapest path from start to n currently known.
gScore := map with default value of Infinity
gScore := 0
// For node n, fScore := gScore + h. fScore represents our current best guess as to
// how short a path from start to finish can be if it goes through n.
fScore := map with default value of Infinity
fScore := h
while openSet is not empty
// This operation can occur in O time if openSet is a min-heap or a priority queue
current := the node in openSet having the lowest fScore value
if current = goal
return reconstruct_path
openSet.Remove
for each neighbor of current
// d is the weight of the edge from current to neighbor
// tentative_gScore is the distance from start to the neighbor through current
tentative_gScore := gScore + d
if tentative_gScore < gScore
// This path to neighbor is better than any previous one. Record it!
cameFrom := current
gScore := tentative_gScore
fScore := gScore + h
if neighbor not in openSet
openSet.add
// Open set is empty but goal was never reached
return failure
Remark: In this pseudocode, if a node is reached by one path, removed from openSet, and subsequently reached by a cheaper path, it will be added to openSet again. This is essential to guarantee that the path returned is optimal if the heuristic function is admissible but not consistent. If the heuristic is consistent, when a node is removed from openSet the path to it is guaranteed to be optimal so the test ‘tentative_gScore < gScore’ will always fail if the node is reached again.
problem. The empty circles represent the nodes in the open set, i.e., those that remain to be explored, and the filled ones are in the closed set. Color on each closed node indicates the distance from the start: the greener, the farther. One can first see the A* moving in a straight line in the direction of the goal, then when hitting the obstacle, it explores alternative routes through the nodes from the open set.
Example
An example of an A* algorithm in action where nodes are cities connected with roads and h is the straight-line distance to target point:Key: green: start; blue: goal; orange: visited
The A* algorithm also has real-world applications. In this example, edges are railroads and h is the great-circle distance to the target. The algorithm is searching for a path between Washington, D.C. and Los Angeles.
Implementation details
There are a number of simple optimizations or implementation details that can significantly affect the performance of an A* implementation. The first detail to note is that the way the priority queue handles ties can have a significant effect on performance in some situations. If ties are broken so the queue behaves in a LIFO manner, A* will behave like depth-first search among equal cost paths.When a path is required at the end of the search, it is common to keep with each node a reference to that node's parent. At the end of the search these references can be used to recover the optimal path. If these references are being kept then it can be important that the same node doesn't appear in the priority queue more than once. A standard approach here is to check if a node about to be added already appears in the priority queue. If it does, then the priority and parent pointers are changed to correspond to the lower cost path. A standard binary heap based priority queue does not directly support the operation of searching for one of its elements, but it can be augmented with a hash table that maps elements to their position in the heap, allowing this decrease-priority operation to be performed in logarithmic time. Alternatively, a Fibonacci heap can perform the same decrease-priority operations in constant amortized time.
Special cases
, as another example of a uniform-cost search algorithm, can be viewed as a special case of A* where for all x. General depth-first search can be implemented using A* by considering that there is a global counter C initialized with a very large value. Every time we process a node we assign C to all of its newly discovered neighbors. After each single assignment, we decrease the counter C by one. Thus the earlier a node is discovered, the higher its value. Both Dijkstra's algorithm and depth-first search can be implemented more efficiently without including an value at each node.Properties
Termination and Completeness
On finite graphs with non-negative edge weights A* is guaranteed to terminate and is complete, i.e. it will always find a solution if one exists. On infinite graphs with a finite branching factor and edge costs that are bounded away from zero, A* is guaranteed to terminate only if there exists a solution.Admissibility
A search algorithm is said to be admissible if it is guaranteed to return an optimal solution. If the heuristic function used by A* is admissible, then A* is admissible. An intuitive ″proof″ of this is as follows:When A* terminates its search, it has found a path from start to goal whose actual cost is lower than the estimated cost of any path from start to goal through any open node. When the heuristic is admissible, those estimates are optimistic, so A* can safely ignore those nodes because they cannot possibly lead to a cheaper solution than the one it already has. In other words, A* will never overlook the possibility of a lower-cost path from start to goal and so it will continue to search until no such possibilities exist.
The actual proof is a bit more involved because the values of open nodes are not guaranteed to be optimistic even if the heuristic is admissible. This is because the values of open nodes are not guaranteed to be optimal, so the sum is not guaranteed to be optimistic.
Optimal efficiency
Algorithm A is optimally efficient with respect to a set of alternative algorithms Alts on a set of problems P if for every problem P in P and every algorithm A′ in Alts, the set of nodes expanded by A in solving P is a subset of the set of nodes expanded by A′ in solving P. The definitive study of the optimal efficiency of A* is due to Rina Dechter and Judea Pearl.They considered a variety of definitions of Alts and P in combination with A*'s heuristic being merely admissible or being both consistent and admissible. The most interesting positive result they proved is that A*, with a consistent heuristic, is optimally efficient with respect to all admissible A*-like search algorithms on all ″non-pathological″ search problems. Roughly speaking, their notion of non-pathological problem is what we now mean by ″up to tie-breaking″. This result does not hold if A*'s heuristic is admissible but not consistent. In that case, Dechter and Pearl showed there exist admissible A*-like algorithms that can expand arbitrarily fewer nodes than A* on some non-pathological problems.
Optimal efficiency is about the set of nodes expanded, not the number of node expansions. When the heuristic being used is admissible but not consistent, it is possible for a node to be expanded by A* many times, an exponential number of times in the worst case.
In such circumstances Dijkstra's algorithm could outperform A* by a large margin.
Bounded relaxation
While the admissibility criterion guarantees an optimal solution path, it also means that A* must examine all equally meritorious paths to find the optimal path. To compute approximate shortest paths, it is possible to speed up the search at the expense of optimality by relaxing the admissibility criterion. Oftentimes we want to bound this relaxation, so that we can guarantee that the solution path is no worse than times the optimal solution path. This new guarantee is referred to as ε-admissible.There are a number of ε-admissible algorithms:
- Weighted A*/Static Weighting. If ha is an admissible heuristic function, in the weighted version of the A* search one uses, as the heuristic function, and perform the A* search as usual. The path hence found by the search algorithm can have a cost of at most ε times that of the least cost path in the graph.
- Dynamic Weighting uses the cost function, where, and where is the depth of the search and N is the anticipated length of the solution path.
- Sampled Dynamic Weighting uses sampling of nodes to better estimate and debias the heuristic error.
- . uses two heuristic functions. The first is the FOCAL list, which is used to select candidate nodes, and the second hF is used to select the most promising node from the FOCAL list.
- Aε selects nodes with the function, where A and B are constants. If no nodes can be selected, the algorithm will backtrack with the function, where C and D are constants.
- AlphA* attempts to promote depth-first exploitation by preferring recently expanded nodes. AlphA* uses the cost function, where, where λ and Λ are constants with, π is the parent of n, and ñ is the most recently expanded node.
Complexity
The heuristic function has a major effect on the practical performance of A* search, since a good heuristic allows A* to prune away many of the nodes that an uninformed search would expand. Its quality can be expressed in terms of the effective branching factor, which can be determined empirically for a problem instance by measuring the number of nodes expanded,, and the depth of the solution, then solving
Good heuristics are those with low effective branching factor.
The time complexity is polynomial when the search space is a tree, there is a single goal state, and the heuristic function h meets the following condition:
where is the optimal heuristic, the exact cost to get from to the goal. In other words, the error of will not grow faster than the logarithm of the "perfect heuristic" that returns the true distance from to the goal.
The space complexity of A* is roughly the same as that of all other graph search algorithms, as it keeps all generated nodes in memory. In practice, this turns out to be the biggest drawback of A* search, leading to the development of memory-bounded heuristic searches, such as Iterative deepening A*, memory bounded A*, and SMA*.
Applications
A* is often used for the common pathfinding problem in applications such as video games, but was originally designed as a general graph traversal algorithm.It finds applications in diverse problems, including the problem of parsing using stochastic grammars in NLP.
Other cases include an Informational search with online learning.
Relations to other algorithms
What sets A* apart from a greedy best-first search algorithm is that it takes the cost/distance already traveled,, into account.Some common variants of Dijkstra's algorithm can be viewed as a special case of A* where the heuristic for all nodes; in turn, both Dijkstra and A* are special cases of dynamic programming.
A* itself is a special case of a generalization of branch and bound.
Variants
- Anytime A* or Anytime Repairing A*
- Anytime Dynamic A*
- Block A*
- D*
- Field D*
- Fringe
- Fringe Saving A*
- Generalized Adaptive A*
- Incremental heuristic search
- Informational search
- Iterative deepening A*
- Jump point search
- Lifelong Planning A*
- Multiplier-accelerated A*
- New Bidirectional A*
- Simplified Memory bounded A*
- Realtime A*
- Theta*
- Time-Bounded A*