第4章:路径搜索算法:A*算法原理与实现、Dijkstra算法、JPS算法、算法对比与选型

各位同学,欢迎来到路径搜索算法的核心章节。

说实话,做无人机航迹规划这么多年,我见过太多新手一上来就抱着A*不放。但实际项目中,选错算法的代价往往是炸机或者算力爆炸。今天咱们就把A*、Dijkstra、JPS这三兄弟掰开揉碎了讲清楚。

4.1 算法家族图谱

先给大家画个知识框架。这三种算法本质上都是在解决同一个问题:在带权重的图中,找到从起点到终点的最优路径。但它们的脾气秉性完全不同。

路径搜索算法 Dijkstra算法 A*算法 JPS算法 • 无启发式 • 保证最优解 • 搜索范围大 • 启发式搜索 • 保证最优解 • 效率较高 • 跳跃搜索 • 保证最优解 • 效率最高 适用:小规模地图 权重非负场景 适用:中等规模 通用场景 适用:大规模 栅格地图

4.2 Dijkstra算法:最朴素的探索者

Dijkstra算法,说白了就是「地毯式搜索」。它从起点开始,一层一层往外扩散,直到找到终点。我刚开始做航迹规划时,第一个用的就是它。

核心思想很简单:维护一个距离表,每次选当前距离最小的节点去扩展。就像你站在迷宫中心,拿粉笔在地上画圈,一圈一圈往外画,直到画到出口。

算法精髓:贪心策略 + 动态规划。每次选「当前已知最短路径」的节点进行松弛操作。

// Dijkstra 伪代码
function Dijkstra(graph, start, end):
    dist[start] = 0
    for each node v != start:
        dist[v] = INF
    
    while priority_queue is not empty:
        u = queue.pop()  // 取距离最小的节点
        if u == end: break
        
        for each neighbor v of u:
            alt = dist[u] + weight(u, v)
            if alt < dist[v]:
                dist[v] = alt
                queue.push(v, alt)
    
    return dist[end]

注意:Dijkstra要求所有边的权重非负。我曾经在一个项目中忽略了这一点,结果路径规划出来全是负数权重绕圈圈,差点把无人机导到禁飞区去。

4.3 A*算法:带指南针的探索者

A*算法就是在Dijkstra的基础上加了个「指南针」——启发式函数。这个启发式函数告诉算法:「嘿,往这个方向走更有可能找到终点」。

我个人习惯用欧几里得距离作为启发式,但在栅格地图上,曼哈顿距离往往更实用。为什么?因为无人机在栅格地图上只能上下左右移动,直线距离反而会误导。

// A* 伪代码
function AStar(graph, start, end):
    openSet = {start}
    gScore[start] = 0
    fScore[start] = heuristic(start, end)
    
    while openSet is not empty:
        current = node in openSet with lowest fScore
        if current == end: return reconstruct_path(cameFrom, current)
        
        openSet.remove(current)
        for each neighbor of current:
            tentative_g = gScore[current] + weight(current, neighbor)
            if tentative_g < gScore[neighbor]:
                cameFrom[neighbor] = current
                gScore[neighbor] = tentative_g
                fScore[neighbor] = gScore[neighbor] + heuristic(neighbor, end)
                if neighbor not in openSet:
                    openSet.add(neighbor)
    
    return failure

避坑指南:我曾经在复杂地形中直接用欧几里得距离做启发式,结果A*绕了远路。后来改用「对角线距离」——允许斜向移动时,这个启发式更准确。你想想看,无人机又不是只能走直线,斜着飞更省时间。

4.4 JPS算法:会跳的搜索者

JPS(Jump Point Search)算法,说白了就是A*的「加速版」。它利用栅格地图的对称性,跳过那些「没必要探索」的格子。

核心思想:在开阔区域,很多格子其实不用一个个看。比如你面前是一条笔直的走廊,你只需要看走廊尽头,中间那些格子直接跳过。

// JPS 核心:寻找跳跃点
function jump(current, direction, end):
    next = current + direction
    if next is obstacle or out of bounds: return null
    if next == end: return next
    
    // 检查强制邻居
    if has_forced_neighbor(next, direction):
        return next
    
    // 对角线移动时,检查水平和垂直方向
    if direction is diagonal:
        if jump(next, (direction.x, 0), end) or jump(next, (0, direction.y), end):
            return next
    
    return jump(next, direction, end)

关键点:JPS只在栅格地图上有效。如果你的地图是路网图或者连续空间,JPS就派不上用场了。我记得有次在三维空域规划中硬套JPS,结果算法直接崩溃——因为三维空间没有「对称性」可以利用。

4.5 算法对比与选型

好了,三个算法都讲完了。咱们来做个对比,方便你选型。

特性 Dijkstra A* JPS
启发式 有(需设计) 有(同A*)
最优性 保证 保证(可采纳启发式) 保证
时间复杂度 O(V²) 或 O(E log V) O(E log V) 实际更快 O(E log V) 实际最快
适用地图 任意图 任意图 仅栅格地图
内存占用
实现难度 简单 中等 较难

选型建议:

  • 小规模地图(100x100以内):用Dijkstra就行,简单可靠。我早期做演示项目时就用它,客户看着路径一圈圈扩散,觉得「很专业」。
  • 中等规模(1000x1000以内):上A*。启发式函数选曼哈顿距离,大部分场景够用。
  • 大规模栅格地图(10000x10000以上):必须用JPS。我做过一个城市级无人机配送项目,地图20000x20000,A*跑一次要3秒,换成JPS后降到0.2秒。

重要提醒:JPS虽然快,但有个致命弱点——它依赖「规则障碍物」。如果地图中有大量不规则障碍(比如森林、建筑群),JPS的跳跃点会变得很少,性能优势就没了。我曾经在山区地形中吃过这个亏,后来老老实实换回A*。

4.6 实战经验总结

最后分享几个我踩过的坑:

  • 启发式函数别太贪心:启发式越接近真实距离,A*越快。但如果你用「完美启发式」(比如预计算所有节点到终点的距离),那A*就退化成Dijkstra了。嗯,这里要注意平衡。
  • 内存管理:JPS虽然搜索快,但跳跃点预处理需要额外内存。我建议在初始化阶段就把跳跃点算好存起来,别每次搜索都重新算。
  • 动态障碍物:如果地图中有移动障碍物(比如其他无人机),JPS的预处理就失效了。这时候我建议用D* Lite或者RRT*,不过那是后面章节的内容了。

好了,路径搜索算法的核心就这些。记住:没有最好的算法,只有最合适的算法。选型时多想想你的地图长什么样、实时性要求多高、内存够不够用。


公众号:蓝海资料掘金营,微信deep3321