图算法(Dijkstra / A*)实现最短路径与游戏寻路
本节目标
- 用 Dijkstra 在网格图上做最短路径
- 理解 A* 的"启发式"如何加速寻路
- 体会图算法在游戏 / 地图中的真实应用
网格寻路:把地图格子当作图节点,四连通(上下左右)为边,移动代价为 1。Dijkstra 在此退化为 BFS(边权相等)。A* 在此基础上加"启发式"——估算到终点的剩余代价,优先探索"看起来更近"的方向。
// 运行环境:Node.js 14+
// 保存为 dsa-l25.js,执行:node dsa-l25.js
// 0=可走 1=障碍;返回从 (0,0) 到 (m-1,n-1) 的最短步数(Dijkstra/BFS)
function shortestPath(grid) {
const m = grid.length, n = grid[0].length
const dist = Array.from({ length: m }, () => new Array(n).fill(Infinity))
dist[0][0] = 0
const q = [[0, 0]] // 简单队列即可(边权=1)
const dirs = [[1,0],[-1,0],[0,1],[0,-1]]
while (q.length) {
const [x, y] = q.shift()
for (const [dx, dy] of dirs) {
const nx = x + dx, ny = y + dy
if (nx<0||ny<0||nx>=m||ny>=n||grid[nx][ny]===1) continue
if (dist[x][y] + 1 < dist[nx][ny]) { dist[nx][ny] = dist[x][y] + 1; q.push([nx, ny]) }
}
}
return dist[m-1][n-1]
}
// A*:加曼哈顿距离启发式 h,用"f=g+h"最小优先(演示用数组代替堆)
function astar(grid) {
const m = grid.length, n = grid[0].length
const h = (x, y) => (m - 1 - x) + (n - 1 - y) // 曼哈顿距离到终点
const g = Array.from({ length: m }, () => new Array(n).fill(Infinity))
g[0][0] = 0
const open = [{ x: 0, y: 0, f: h(0, 0) }]
const dirs = [[1,0],[-1,0],[0,1],[0,-1]]
while (open.length) {
let bi = 0; for (let i = 1; i < open.length; i++) if (open[i].f < open[bi].f) bi = i
const { x, y } = open.splice(bi, 1)[0] // 取 f 最小
if (x === m-1 && y === n-1) return g[x][y]
for (const [dx, dy] of dirs) {
const nx = x+dx, ny = y+dy
if (nx<0||ny<0||nx>=m||ny>=n||grid[nx][ny]===1) continue
const ng = g[x][y] + 1
if (ng < g[nx][ny]) { g[nx][ny] = ng; open.push({ x: nx, y: ny, f: ng + h(nx, ny) }) }
}
}
return Infinity
}
const map = [
[0,0,0,0],
[1,1,0,1],
[0,0,0,0],
[0,1,1,0],
]
console.log('Dijkstra/BFS 步数:', shortestPath(map)) // 8
console.log('A* 步数:', astar(map)) // 8A vs Dijkstra:Dijkstra 盲目向四周扩散;A 用启发式 h 引导方向,扩展节点更少、更快到达终点(只要 h 不高估真实代价)。
名词解释
- 启发式函数(Heuristic):对"从当前状态到目标还需多少代价"的估算。A* 用
f = g + h(已花代价 + 预估剩余)决定下一步,h 越准越快;但 h 不能"高估"真实代价,否则可能错过最优解。 - 曼哈顿距离(Manhattan Distance):网格中只能上下左右走时,两点距离 = |Δx| + |Δy|。是网格寻路最自然的启发式。
- A*:在 Dijkstra 基础上加启发式引导的寻路算法,是游戏 AI、地图导航的标配(如"人物自动寻路避开障碍")。
课后练习
- 为什么网格图(边权=1)Dijkstra 等价于 BFS?
- 答案:边权都相等时,最先出队的必是步数最少的节点,与 BFS"逐层扩展"完全一致,故可简单用队列代替优先队列。
- A* 的启发式
h为什么不能高估?- 答案:若 h 高估了剩余代价,A* 可能提前选"看起来近但其实绕远"的路径并锁定,从而得不到真正最短解;保持"不高估"(可采纳/ admissible)才能保证最优。
总结
图算法在这一节落地成了你能"看得见"的东西——游戏里小人对障碍绕行、地图 App 给你规划路线,内核都是最短路径。Dijkstra 在网格上会退化成 BFS,这本身是个很好的认知桥:当所有边权相等,"最近优先"就足够。而 A* 则是工程智慧的结晶:它在 Dijkstra 的"盲目扩散"上叠加了"启发式引导",像人找路时会朝目的地方向走而不是乱转,从而大幅减少探索的节点。关键约束是启发式不能高估——这一点极具哲理,它提醒我们"带着先验方向走"能加速,但"自以为是的方向"会让你错过最优。从 Dijkstra 到 A*,你学到的不只是一个算法,而是"如何在搜索中聪明地剪枝"这一贯穿 AI、运筹、游戏的核心思想。