A*搜索算法
A*(A-Star)算法是一种启发式搜索算法,用于在图中找到从起点到终点的最短路径。它结合了Dijkstra算法的完备性和贪心最佳优先搜索的效率。1
核心公式
A*算法使用评估函数 来选择下一个扩展的节点:
其中:
- :从起点到当前节点 的实际代价
- :启发函数,从节点 到终点的估计代价
- :通过节点 到达终点的总估计代价
启发函数的要求
1. Admissible(可采纳性)
启发函数 必须是可采纳的,即它永远不会高估从 到终点的实际代价:
其中 是实际最小代价。
2. Consistency(一致性)或 monotonicity(单调性)
对于任意节点 和其后继节点 :
其中 是边 的代价。
算法步骤
struct Node {
int x, y;
int g, h, f;
bool operator<(const Node& other) const {
return f > other.f; // 小顶堆
}
};
int astar(const vector<vector<int>>& grid, pair<int,int> start, pair<int,int> end) {
int n = grid.size(), m = grid[0].size();
vector<vector<int>> dist(n, vector<int>(m, INT_MAX));
priority_queue<Node> pq;
auto heuristic = [&](int x, int y) {
return abs(x - end.first) + abs(y - end.second); // Manhattan距离
};
dist[start.first][start.second] = 0;
pq.push({start.first, start.second, 0, heuristic(start.first, start.second),
heuristic(start.first, start.second)});
int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, 1, -1};
while (!pq.empty()) {
Node cur = pq.top();
pq.pop();
if (cur.x == end.first && cur.y == end.second) {
return cur.g;
}
for (int dir = 0; dir < 4; ++dir) {
int nx = cur.x + dx[dir];
int ny = cur.y + dy[dir];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && grid[nx][ny] == 0) {
int newG = cur.g + 1;
if (newG < dist[nx][ny]) {
dist[nx][ny] = newG;
int h = heuristic(nx, ny);
pq.push({nx, ny, newG, h, newG + h});
}
}
}
}
return -1; // 无路径
}常见启发函数
| 问题类型 | 启发函数 | 公式 |
|---|---|---|
| 网格移动(4方向) | Manhattan距离 | |
| 网格移动(8方向) | Chebyshev距离 | |
| 任意方向移动 | Euclidean距离 | |
| 滑块 puzzle | Hamming距离 | 不在正确位置的棋子数 |
A*与Dijkstra、BFS的关系
| 算法 | 评估函数 | 特点 |
|---|---|---|
| BFS | (层数) | 找到最短路径,无启发 |
| Dijkstra | 找到最短路径,均匀扩展 | |
| 贪心最佳优先 | 最快找到解,不保证最优 | |
| A* | 综合两者,保证最优 |
正确性证明思路
A*算法保证找到最短路径当且仅当启发函数是可采纳的:
- 最优路径代价 :从起点 到目标的最优路径代价
- 扩展条件:只有 的节点才会被扩展
- 终止证明:当目标节点从优先队列弹出时,其 值等于
应用场景
- 路径规划:游戏中的寻路、机器人导航
- 八皇后/八数码问题:经典启发式搜索问题
- 迷宫求解:带障碍的最短路径
- 调度问题:任务分配、路由选择
参考
Footnotes
-
本段参考A*搜索算法 - OI Wiki ↩