A* 搜索和 Dijkstra 有什么区别?启发函数为什么重要?
简化版
A* 可以看成带启发函数的 Dijkstra。
Dijkstra 按当前已知距离 g(n) 扩展节点;A* 按 f(n) = g(n) + h(n) 扩展,其中 h(n) 是从当前节点到目标的估计距离。
如果启发函数不高估真实距离,A* 可以保证找到最短路径,并且通常比 Dijkstra 少扩展很多节点。
详细版
A* 常用于地图寻路。
| 符号 | 含义 |
|---|---|
g(n) | 起点到当前节点的真实已知代价 |
h(n) | 当前节点到目标的估计代价 |
f(n) | g(n) + h(n),优先队列排序依据 |
Dijkstra 可以看成 h(n) = 0 的 A*。
启发函数越接近真实距离,搜索越有方向;但如果高估真实代价,可能破坏最短路径保证。
完整版教学
1. Dijkstra 的扩展方式
Dijkstra 从起点开始,每次扩展当前 dist 最小的节点。
它不关心目标在哪,只是均匀地向外扩散。
在地图中,如果目标在右上角,Dijkstra 仍可能向左下方扩展很多无关节点。
这就是 A* 想优化的地方。
2. A* 的核心公式
A* 给每个节点一个估价:
f(n) = g(n) + h(n)
其中:
g(n)是起点到n的已知代价;h(n)是n到目标的估计代价;f(n)是经过n到目标的总估计代价。
A* 的本质是让最短路径搜索带上“朝目标走”的方向感。
3. 启发函数 h 为什么重要
如果 h 太小,比如一直为 0,A* 就退化成 Dijkstra。
如果 h 很接近真实距离,搜索会更集中。
如果 h 高估真实距离,算法可能过早放弃真正最短路径方向。
| h 的性质 | 影响 |
|---|---|
h = 0 | 退化为 Dijkstra |
| 不高估真实距离 | 保证最优性 |
| 接近真实距离 | 扩展节点更少 |
| 高估真实距离 | 可能不保证最短 |
4. 什么叫可采纳启发函数
可采纳启发函数要求:
h(n) <= 从 n 到目标的真实最短距离
也就是不能高估。
例如在四方向网格中,如果每步代价为 1,曼哈顿距离通常是可采纳的:
abs(x1 - x2) + abs(y1 - y2)
因为你至少要走这么多水平和垂直步。
5. 常见启发函数怎么选
不同移动规则对应不同启发函数。
| 场景 | 常见 h |
|---|---|
| 四方向网格 | 曼哈顿距离 |
| 八方向网格 | 对角距离或欧几里得距离 |
| 地图导航 | 直线距离 |
| 状态搜索 | 问题特定下界 |
选择启发函数时,要保证它是目标距离的合理下界。
6. A* 的数据结构
A* 通常也用优先队列。
队列按 f(n) 排序:
openSet.push(node, priority = g[node] + h[node])
还需要记录:
gScore:起点到节点的当前最好代价;cameFrom:用于恢复路径;- closed 集合:已经处理过的节点。
7. 和 Dijkstra 怎么对比
可以这样说:
| 维度 | Dijkstra | A* |
|---|---|---|
| 排序依据 | g(n) | g(n) + h(n) |
| 是否需要目标 | 不强依赖 | 依赖目标 |
| 搜索方向 | 均匀扩散 | 朝目标引导 |
| 最优性条件 | 非负权边 | 非负权边 + 合理 h |
如果没有明确目标,A* 的启发意义不大;如果目标明确,A* 往往更高效。
8. 常见误区与追问
- 误区:A 一定比 Dijkstra 快。* 如果启发函数很差,A* 可能接近 Dijkstra,甚至有额外开销。
- 误区:启发函数越大越好。 高估真实距离可能破坏最短路径保证。
- 误区:A 不需要优先队列。* 实现上通常仍然依赖优先队列按
f取最小。 - 追问:Dijkstra 是 A 的特例吗?* 可以看成
h(n)=0的 A*。 - 追问:曼哈顿距离什么时候适合? 适合四方向网格、每步代价一致的场景。