This article introduces the A* search algorithm.
A* search algorithm (the A* search algorithm; A* is pronounced A-star), or simply the A* algorithm, is an algorithm for finding the shortest path between a given start point and endpoint in a weighted directed graph. It belongs to graph traversal and best-first search algorithms, and is also an improvement over BFS.
Process
The goal of the A* algorithm is to find the shortest path from start point to endpoint in a directed graph. Let be the distance between vertices and , that is, the length of their shortest path. Let be the distance function from start point to vertex , and let be the distance function from vertex to endpoint . Let be an estimate of 1. Finally, let the estimated shortest-path length from to through be
During the search, the A* algorithm takes a vertex with minimum from the priority queue each time. It then pushes all of its successor vertices into the priority queue and updates using the actually recorded and estimated .
Properties
Because the actual value of is unknown during the search, an easily computed must be used as its estimate. The actual complexity of A* search depends on the properties of this estimate function . It is easy to imagine that if , meaning the estimate is exact, the search will proceed strictly along the shortest path. If , the A* algorithm degenerates into Dijkstra's algorithm; when and all edge weights are , it is BFS.
Assume the graph has no negative-weight edges. If the estimate never exceeds the actual distance , that is, , the A* algorithm is guaranteed to find an optimal solution. An estimate function satisfying this condition is called admissible. As discussed above, the closer is to , the more efficient the corresponding A* algorithm is. Generally, in the worst case, the algorithm visits all vertices satisfying
where is the shortest distance between start point and endpoint . Intuitively, the closer is to , the fewer successor vertices satisfy this condition at each expansion, so the fewer branches the algorithm searches. Thus, the A* algorithm can be regarded as a “pruning” optimization for search algorithms.
If is not only admissible but also consistent, that is,
then the A* algorithm will not add a vertex that has already been popped from the queue back into the queue. The consistency condition can be understood as the triangle inequality among vertices .
Examples
A classic application of the A* algorithm is solving the k-th shortest path problem. For a description of the problem, the A* approach, and a persistent leftist-heap approach with better complexity, see the k-th shortest path problem page.
This section introduces a classic problem that can be solved with the A* algorithm.
8-Puzzle
An chessboard contains eight pieces, each marked with a number from to . One square is empty, represented by . A piece next to the empty square can be moved into it, making the piece's original position empty. Given an initial layout and a target layout (to simplify the problem, let the target state be the following), find a way to move from the initial layout to the target layout in the minimum number of steps.
Solution Idea
The function can be defined as the number of pieces not in their correct positions. It is easy to see that is both admissible and consistent. This problem can be solved with the A* algorithm.
Reference Code
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#include <set>
using namespace std;
constexpr int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
int fx, fy;
char ch;
struct matrix {
int a[5][5];
bool operator<(matrix x) const {
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 3; j++)
if (a[i][j] != x.a[i][j]) return a[i][j] < x.a[i][j];
return false;
}
} f, st;
int h(matrix a) {
int ret = 0;
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 3; j++)
if (a.a[i][j] != st.a[i][j] && a.a[i][j] != 0) ret++;
return ret;
}
struct node {
matrix a;
int t;
bool operator<(node x) const { return t + h(a) > x.t + h(x.a); }
} x;
priority_queue<node> q; // Search queue
set<matrix> s; // Prevent duplicate states in the search queue
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
st.a[1][1] = 1; // Define the standard matrix
st.a[1][2] = 2;
st.a[1][3] = 3;
st.a[2][1] = 8;
st.a[2][2] = 0;
st.a[2][3] = 4;
st.a[3][1] = 7;
st.a[3][2] = 6;
st.a[3][3] = 5;
for (int i = 1; i <= 3; i++) // Input
for (int j = 1; j <= 3; j++) {
cin >> ch;
f.a[i][j] = ch - '0';
}
s.insert(f);
q.push({f, 0});
while (!q.empty()) {
x = q.top();
q.pop();
if (!h(x.a)) { // Check whether it matches the standard matrix
cout << x.t << '\n';
return 0;
}
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 3; j++)
if (!x.a.a[i][j]) fx = i, fy = j; // Find the position of the empty square (vertex 0)
for (int i = 0; i < 4; i++) { // Search in each of the four movement directions
int xx = fx + dx[i], yy = fy + dy[i];
if (1 <= xx && xx <= 3 && 1 <= yy && yy <= 3) {
swap(x.a.a[fx][fy], x.a.a[xx][yy]);
if (!s.count(x.a))
s.insert(x.a),
q.push({x.a, x.t + 1}); // Put the new state into the search queue
swap(x.a.a[fx][fy], x.a.a[xx][yy]); // The case without this move
}
}
}
return 0;
}
References and notes
Footnotes
-
Here means heuristic. See Heuristic search - Wikipedia and the Bounded relaxation section of A* search algorithm - Wikipedia. ↩