3. 全局路径规划之Dijkstra算法:算法原理、伪代码、Python实现、优缺点分析

3.1 算法原理:从点A到点B的最短路径怎么找?

Dijkstra算法,说白了就是一张地图上,从起点到所有其他点的最短路径怎么算。它由荷兰计算机科学家Edsger Dijkstra在1956年提出,到现在快70年了,依然是路径规划的基石。

我个人习惯把它理解成「水波扩散」的过程。你往平静的水面扔一颗石子,波纹会一圈一圈往外扩散。Dijkstra算法也是这样——从起点开始,一层一层往外探索,每次只走当前已知最短的那条路。

核心思想其实就一句话:每次从未访问的节点中,选一个距离起点最近的节点,然后更新它邻居的距离。嗯,就这么简单。

我当年在做一个仓储机器人项目时,第一次亲手实现这个算法。当时觉得不就是个BFS加权重嘛,结果调试了一整天——因为没处理好负权边的问题。这个坑后面我会细说。

3.2 算法伪代码:清晰到可以直接抄

先看伪代码,我习惯用这种形式来理清思路。你想想看,写代码前把逻辑捋顺了,后面能少踩多少坑。

function Dijkstra(Graph, source):
    // 初始化
    dist[source] = 0
    for each vertex v in Graph:
        if v != source:
            dist[v] = INFINITY
        prev[v] = UNDEFINED
        add v to unvisited_set
    
    // 主循环
    while unvisited_set is not empty:
        u = vertex with smallest dist in unvisited_set
        remove u from unvisited_set
        
        for each neighbor v of u:
            alt = dist[u] + weight(u, v)
            if alt < dist[v]:
                dist[v] = alt
                prev[v] = u
    
    return dist[], prev[]

这里有个细节要注意:prev[]数组是用来回溯路径的。从终点开始,沿着prev一直往前找,就能得到完整的路径。我在项目中经常用这个技巧来调试路径规划的结果。

3.3 Python实现:能跑起来的代码才是好代码

下面是我写的一个简洁版本。为了让你看得更清楚,我特意没用任何第三方库,纯Python实现。

import heapq

def dijkstra(graph, start):
    """
    graph: 邻接表表示的图,格式 {node: [(neighbor, weight), ...]}
    start: 起点
    返回: (dist, prev)
    """
    # 初始化
    dist = {node: float('inf') for node in graph}
    dist[start] = 0
    prev = {node: None for node in graph}
    
    # 优先队列,存储 (距离, 节点)
    pq = [(0, start)]
    visited = set()
    
    while pq:
        current_dist, u = heapq.heappop(pq)
        
        # 如果已经访问过,跳过
        if u in visited:
            continue
        visited.add(u)
        
        # 遍历邻居
        for v, weight in graph[u]:
            if v in visited:
                continue
            alt = current_dist + weight
            if alt < dist[v]:
                dist[v] = alt
                prev[v] = u
                heapq.heappush(pq, (alt, v))
    
    return dist, prev

def reconstruct_path(prev, start, end):
    """回溯路径"""
    path = []
    current = end
    while current is not None:
        path.append(current)
        current = prev[current]
    path.reverse()
    return path if path[0] == start else []

# 使用示例
if __name__ == "__main__":
    # 一个简单的图
    graph = {
        'A': [('B', 4), ('C', 2)],
        'B': [('A', 4), ('C', 1), ('D', 5)],
        'C': [('A', 2), ('B', 1), ('D', 8), ('E', 10)],
        'D': [('B', 5), ('C', 8), ('E', 2), ('F', 6)],
        'E': [('C', 10), ('D', 2), ('F', 3)],
        'F': [('D', 6), ('E', 3)]
    }
    
    dist, prev = dijkstra(graph, 'A')
    path = reconstruct_path(prev, 'A', 'F')
    print(f"最短路径: {path}")
    print(f"最短距离: {dist['F']}")
我的小技巧:heapq实现优先队列,比每次手动找最小距离快得多。在节点数超过1000时,这个优化能让运行时间从秒级降到毫秒级。

3.4 优缺点分析:没有银弹

任何算法都有它的适用场景。Dijkstra算法虽然经典,但也不是万能的。我把它优缺点列出来,你心里有个数。

优点 缺点
保证找到最短路径(正权图) 不能处理负权边
算法稳定,结果可预测 时间复杂度较高 O((V+E)logV)
实现简单,容易调试 在大规模图上效率低
适用于静态环境 动态环境需要重新计算
可以同时得到到所有点的最短路径 如果只需要到某个点,仍然要遍历全图
避坑指南:我曾经在一个AGV调度项目里,直接用Dijkstra做全局路径规划。地图有5000多个节点,每次规划要跑200多毫秒。后来换成A*算法,同样的地图只需要20毫秒。所以,如果你只需要找两个点之间的最短路径,别用Dijkstra,用A*。

3.5 实际应用中的注意事项

说几个我在实战中踩过的坑,希望能帮你省点时间。

  • 图太大怎么办? 可以用双向Dijkstra——从起点和终点同时开始搜索,能减少一半的搜索空间。
  • 地图是网格怎么办? 把每个格子当成一个节点,上下左右相邻的格子之间有权重。我习惯用曼哈顿距离或者欧几里得距离作为权重。
  • 路径不平滑怎么办? Dijkstra出来的路径是折线,直接给机器人用会一顿一顿的。我一般会在后面加一个路径平滑处理,比如用B样条曲线。
  • 内存不够怎么办? 如果图特别大,可以考虑用邻接表而不是邻接矩阵。邻接矩阵是O(V²)的空间复杂度,邻接表是O(V+E)。

一句话总结:Dijkstra算法是路径规划的「基本功」,就像练武要扎马步一样。虽然实际项目中很少直接用它,但理解它的思想,对你理解A*、D*、RRT等更高级的算法非常有帮助。

好了,这一章就到这里。下一章我们会讲A*算法——说白了就是在Dijkstra的基础上加了一个启发式函数,让搜索更有方向感。到时候我会对比着讲,你就能看出两者的区别了。