交互式图论学习
交互式图论学习
Guest User
Using app without sign in
交互式A*寻路可视化工具
通过启发式函数引导搜索方向,比 Dijkstra 更快找到最短路径
选择算法并生成步骤以开始可视化
A* 在带权图中寻找两点之间代价最小的路径,是绝大多数游戏和机器人寻路背后的算法。它就是 Dijkstra 加上一样东西:对每个节点到目标还有多远的估计。这个估计让搜索朝目标推进,而不是向四面八方均匀铺开。Hart、Nilsson 和 Raphael 于 1968 年发表了该算法。
每个节点携带三个数:g 表示从起点已确认的代价,h 表示到目标的估计剩余代价,f = g + h 表示估计总代价。A* 维护一个已发现节点的开放集合,每次总是扩展 f 最小的那个。扩展意味着把它移入关闭集合,并像 Dijkstra 那样松弛它的边。一旦目标被扩展,搜索立即结束。使用二叉堆时时间复杂度为 O((V + E) log V),与 Dijkstra 相同,但通常触及的节点少得多。
A* 是游戏引擎、仓储机器人和无人机导航中默认的寻路算法,也用于任何能以直线距离作为下界的路径规划。它还可以求解华容道等状态空间搜索问题。在面试中,它是讲完 Dijkstra 之后自然的下一题:考官通常会问启发函数需要满足什么性质,答案才能保持最优。
A* 就是 Dijkstra 多加一项。Dijkstra 每次扩展已确认代价 g 最小的节点,而 A* 扩展 f = g + h 最小的节点,其中 h 估计剩余代价。把 h 处处设为零,下面的伪代码就完全变成了 Dijkstra。
A*(图, 起点, 目标, h):
对每个顶点 v: g[v] = 无穷大
g[起点] = 0
f[起点] = h(起点)
开放集 = 含 (f[起点], 起点) 的优先队列
关闭集 = 空集合
当开放集非空时:
u = 开放集.取最小() // f 最小者
若 u == 目标: 返回 重建路径(u)
将 u 加入关闭集
对每条边 (u, v, w):
若 v 在关闭集中: 跳过
试探值 = g[u] + w
若 试探值 < g[v]:
前驱[v] = u
g[v] = 试探值
f[v] = 试探值 + h(v)
开放集.插入(f[v], v)
返回 无路径第 15 行跳过关闭集中的节点,只有当 h 一致时才安全,即对每条边都满足 h(u) <= w(u, v) + h(v)。若启发函数仅仅是可采纳而不一致,就必须允许节点重新离开关闭集,否则 A* 可能返回非最优路径。上方的可视化工具把直线距离乘以任意边所能提供的最低单位代价,由三角不等式可知这样得到的 h 是一致的,因此无需重新开放任何节点。
五个节点,S 位于原点,目标 G 在其右侧五个单位处。这段轨迹的关键在于 A* 从未触碰的那个节点。
示例图: S(0,0)、A(2,1)、B(2,-1)、C(1,4)、G(5,0)。边 S-A = 3,S-B = 2,S-C = 4,A-B = 2,A-G = 4,B-G = 6,C-G = 7。把直线距离乘以任意边的最低单位代价(0.894),得到 h(S) = 4.47,h(A) = 2.83,h(B) = 2.83,h(C) = 5.06,h(G) = 0。
A* 返回 S 到 A 到 G,代价为 7,共扩展 4 个节点。Dijkstra 在同一张图上返回完全相同的路径与代价,却要扩展 5 个:它必须先处理完 C,才肯确定目标。C 从来都不值得访问,而 h 正是让 A* 无需检查就知道这一点的原因。
时间: 使用二叉堆时最坏情况 O((V + E) log V) · 空间: O(V)
最坏情况与 Dijkstra 相同,原因也一样:每个顶点最多进入优先队列一次,每条边最多触发一次减键操作,即 V 次取出与 E 次更新,每次 O(log V)。启发函数改变不了这个界,它改变的是常数:f 超过目标最终代价的节点根本不会被扩展。当 h = 0 时,A* 精确退化为 Dijkstra;当 h 完美时,它径直沿最优路径前进。在 4000 张随机生成的带权图上,上述实现平均扩展 4.58 个节点,而 Dijkstra 为 5.52 个,并且每一次都返回了最优代价。
只有当你既有明确目标、又有可用的距离估计时,A* 额外的机制才值得。缺少其中任何一项,下面某个算法都是更好的工具。
| 替代算法 | 以下情况更合适 | 代价 |
|---|---|---|
| Dijkstra 算法 | 你需要到所有节点的最短路径,或者没有任何有意义的启发函数。h = 0 的 A* 就是它。 | O((V + E) log V) |
| 广度优先搜索 | 所有边的代价相同。BFS 完全不需要优先队列就能给出同样的答案。 | O(V + E) |
| Bellman-Ford | 存在负权边。A* 继承了 Dijkstra 的非负权假设,在这里会失效。 | O(V * E) |
| 双向 A* | 起点与目标唯一的超大图。从两端同时搜索大致能把探索区域减半。 | O((V + E) log V) |
| 加权 A*(f = g + w*h) | 你愿意用最优性换速度。w > 1 能更快找到路径,但只保证代价在最优值的 w 倍以内。 | O((V + E) log V) |