最短路径算法

最短路径问题

最短路径是图论的核心问题:在加权图中找两节点之间路径权值和最小的路径。按”起点数量”可分为单源(一个起点到所有点)和多源(任意两点间)两大类,按边权又分为非负权与含负权两类。选择正确算法是解这类题的第一步。

算法总览

算法适用条件时间复杂度特点
Dijkstra边权非负O((V+E) log V)(堆优化)单源,贪心思想,最常用
Bellman-Ford允许负权边,不能有负权环O(V·E)单源,可检测负权环
SPFA允许负权边平均 O(E),最坏 O(V·E)Bellman-Ford 队列优化,慎用
Floyd-Warshall任意权值(无负权环)O(V³)多源,动态规划,代码极简

Dijkstra:非负权单源最短路径

贪心策略:每次从未确定最短路的节点中,选择距离起点最近的一个”确认”下来(因为边权非负,它不可能再被其他路径更新得更短),然后松弛它的所有出边。用优先队列加速”取最近节点”:

typedef pair<int,int> pii;
vector<int> dijkstra(vector<vector<pii>>& graph, int start) {
    int n = graph.size();
    vector<int> dist(n, INT_MAX);
    priority_queue<pii, vector<pii>, greater<pii>> pq;

    dist[start] = 0;
    pq.push({0, start});
    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;          // 过期条目,跳过
        for (auto [v, w] : graph[u]) {
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    return dist;
}

要点:d > dist[u] 的过期判断不能省,否则重复松弛会拖慢性能。

Bellman-Ford:含负权边

对所有边做 V−1 轮”松弛”(每轮保证至少多确定一条最短路径的边),第 V 轮若还能松弛,说明存在负权环。O(V·E),能处理负权边并检测负权环。

SPFA 是它的队列优化版(只有被更新过的节点才重新松弛),平均很快但最坏 O(V·E),在构造数据下会超时——竞赛中很多人更倾向 Bellman-Ford 或直接避开负权场景。

Floyd-Warshall:全源最短路径

动态规划:dist[i][j] 表示 i→j 的最短距离,外层枚举中间点 k,内层枚举所有点对尝试经由 k 中转。三层循环,代码极简:

void floyd(vector<vector<int>>& dist) {
    int n = dist.size();
    for (int k = 0; k < n; ++k)
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j)
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}

O(V³),适合 V ≤ 300 左右的稠密图全源问题。

选型指南

场景推荐理由
非负权、单源Dijkstra(堆优化)效率高、稳定,默认首选
含负权边、单源Bellman-Ford能处理负权并检测负权环
全源、节点少Floyd-Warshall代码简单,直接得到所有点对距离
无权图BFSO(V+E),比 Dijkstra 更快

应用实例:地图导航(Dijkstra)、网络路由(Bellman-Ford 处理代价变化)、游戏 AI 寻路(A* 是 Dijkstra 的启发式变种)、社交网络最短距离(BFS)。

滚动至顶部