首页/数据结构/05-graph/最短路径 🔗 在 Obsidian 中打开
数据结构 · 05-graph

最短路径

难度 ★★★★重要度 ★★★★★ 考查频率 高题型 选择 / 综合应用 数据结构/图最短路径DijkstraFloyd
速查
Dijkstra 单源、非负权、$O(|V|^2)$:每步选 dist 最小的未访问顶点松弛;Floyd 多源、无负权回路、$O(|V|^3)$。

速查

项目
主题最短路径
核心概念贪心选距离源点最近的未访问顶点,用它更新其他顶点的距离
时间复杂度Dijkstra $O(|V|^2)$(邻接矩阵);Floyd $O(|V|^3)$
难度⭐⭐⭐⭐
重要性⭐⭐⭐⭐⭐

核心概念

一、Dijkstra 算法(单源最短路径)

适用场景

  • 求一个源点到其他所有顶点的最短路径
  • 不能有负权边

算法思想

贪心策略,每次选择距离源点最近的未访问顶点,用它更新其他顶点的距离。

void Dijkstra(MGraph G, int v0) {
    int dist[MaxVertexNum], path[MaxVertexNum];
    bool visited[MaxVertexNum];
    for (int i = 0; i < G.vexnum; i++) {
        dist[i] = G.edge[v0][i];
        path[i] = (dist[i] < INF) ? v0 : -1;
        visited[i] = false;
    }
    dist[v0] = 0; visited[v0] = true;
    for (int i = 1; i < G.vexnum; i++) {
        int min = INF, u = -1;
        for (int j = 0; j < G.vexnum; j++)
            if (!visited[j] && dist[j] < min) { min = dist[j]; u = j; }
        visited[u] = true;
        for (int w = 0; w < G.vexnum; w++)
            if (!visited[w] && dist[u] + G.edge[u][w] < dist[w]) {
                dist[w] = dist[u] + G.edge[u][w];
                path[w] = u;
            }
    }
}

时间复杂度 $O(|V|^2)$

二、Floyd 算法(多源最短路径)

适用场景

  • 求任意两点之间的最短路径
  • 不能有负权回路

状态转移方程

$$A^{(k)}[i][j] = \min\big(A^{(k-1)}[i][j],\; A^{(k-1)}[i][k] + A^{(k-1)}[k][j]\big)$$

void Floyd(MGraph G) {
    int A[MaxVertexNum][MaxVertexNum];
    for (int i = 0; i < G.vexnum; i++)
        for (int j = 0; j < G.vexnum; j++)
            A[i][j] = G.edge[i][j];
    for (int k = 0; k < G.vexnum; k++)          // k 必须在最外层
        for (int i = 0; i < G.vexnum; i++)
            for (int j = 0; j < G.vexnum; j++)
                if (A[i][k] + A[k][j] < A[i][j])
                    A[i][j] = A[i][k] + A[k][j];
}

时间复杂度 $O(|V|^3)$

三、Dijkstra vs Floyd

特性DijkstraFloyd
类型单源最短路径多源最短路径
时间$O(|V|^2)$$O(|V|^3)$
负权边不允许不允许负权回路
适用一个源点到所有点任意两点

四、最短路径性质

  1. 最优子结构:最短路径的子路径也是最短路径
  2. Dijkstra 不能处理负权边:贪心策略失效
  3. Floyd 不能有负权回路:路径长度无下界

手算示例

1051 3944 62 A B C D E
Dijkstra 示例图(∞ = 999)

例 1:Dijkstra(从 A)

步骤选 udist 更新
初始化$[0, 10, 5, 1, \infty]$
1D(1)$[0, 10, 5, 0, 7]$
2C(5)$[0, 8, 0, 0, 7]$
3E(7)$[0, 8, 0, 0, 0]$
4B(8)$[0, 0, 0, 0, 0]$

最短路径:A→D:1,A→C:5,A→E:7(A→D→E),A→B:8(A→C→B)。

例 2:Floyd(3 顶点)

    0   1   2
0 [ 0   4  11 ]
1 [ 6   0   2 ]
2 [ 3  999  0 ]

依次允许经过中间顶点 0、1、2,最终:

    0   1   2
0 [ 0   4   6 ]
1 [ 5   0   2 ]
2 [ 3   7   0 ]

例如 $A[1][0]=5$ 对应路径 $1→2→0$($2+3=5$)。

常见考法

考法 1:Dijkstra 手算给出图和源点,逐步求到各顶点的最短路径。
考法 2:Floyd 手算给出邻接矩阵,逐步求任意两点最短路径。
考法 3:算法选择一个源点到所有点 → Dijkstra;任意两点 → Floyd。
考法 4:负权边Dijkstra 不能处理负权边。

易错点

注意
  1. Dijkstra 不能有负权边:贪心策略失效
  2. Floyd 三重循环顺序:$k$ 必须在最外层
  3. Floyd 不能有负权回路
  4. 每次选 dist 最小的未访问顶点
  5. 松弛条件:$dist[u] + edge[u][w] < dist[w]$

核心结论

算法时间空间限制
Dijkstra$O(|V|^2)$$O(|V|)$无负权边
Floyd$O(|V|^3)$$O(|V|^2)$无负权回路

记忆卡片

Dijkstra 的时间复杂度?
$O(|V|^2)$。
Floyd 的时间复杂度?
$O(|V|^3)$。
Dijkstra 能处理负权边吗?
不能。
Floyd 中 k 在哪一层?
最外层(中间顶点)。
求任意两点最短路径用什么?
Floyd。
单源最短路径用什么?
Dijkstra(无负权边时)。

交互动画 · Dijkstra(从 A 出发)

10 5 1 3 9 4 6 2 A B C D E
从 A 出发,逐步确定最短距离
点击「播放」或「下一步」

相关知识点

graph-traversal minimum-spanning-tree topological-sort critical-path mst-and-shortest-path

↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。