BFS 与最短路

图论题的"水电煤":可达性用 BFS,非负权用 Dijkstra,多点对用 Floyd

一、怎么选工具

场景算法复杂度
无权图可达性 / 最少步数BFSO(n + m)
边权 0/1(最少修改次数等)0-1 BFS(双端队列)O(n + m)
非负权单源最短路Dijkstra(堆优化)O(m log n)
多点对最短路、n ≤ 400FloydO(n³)
只问"能不能到"且 n ≤ 10⁴Floyd 的 bitset 版O(n³ / 64)
负权边:出现负权就不能用 Dijkstra(会算错),提高级一般考 SPFA 判负环或直接避开,见到负权先警觉。

二、BFS:按层扩散的洪水

从起点出发,队列保证"先发现的点先扩展",因此每个点第一次出队时的步数就是最少步数。应用:

queue<int> q; q.push(s); vis[s] = 1; dist[s] = 0;
while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int v : g[u]) if (!vis[v]) {
        vis[v] = 1; dist[v] = dist[u] + 1; q.push(v);
    }
}
S 1 2 3 4 5 第 0 层:dist = 0 第 1 层:dist = 1 第 2 层:dist = 2
BFS 按层扩展:第一次到达即最少步数

三、Dijkstra:每次锁定最近的那个点

维护 dist[](起点到各点的当前最短路),用小根堆每次取出 dist 最小的未确定点 u——非负权保证它的 dist 已经最优("确定"),再用 u 松弛所有出边:

priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<>> q;
dist[s] = 0; q.push({0, s});
while (!q.empty()) {
    auto [d, u] = q.top(); q.pop();
    if (d != dist[u]) continue;              // 懒删除:过期堆顶直接跳过
    for (auto [v, w] : g[u])
        if (dist[u] + w < dist[v]) {
            dist[v] = dist[u] + w;
            q.push({dist[v], v});
        }
}
直觉:非负权下,离你最近的那个未确定点不可能再有更近的绕法——任何绕行都要先经过更远的点。这就是 Dijkstra 的贪心正确性。

走查一遍

图:1→2(权 2)、1→3(权 5)、2→3(权 1)、3→4(权 3)、2→4(权 6),求 1 到各点最短路。

步骤锁定(dist 最小的未确定点)松弛后 dist[2] / dist[3] / dist[4]
初始—∞ / ∞ / ∞(dist[1]=0)
1①(0)2 / 5 / ∞
2②(2)2 / 3(2+1 比 5 小)/ 8
3③(3)2 / 3 / 6(3+3 比 8 小)
4④(6)结束:1→2=2,1→3=3,1→4=6

每一步锁定的点,它的 dist 从此不再变化——这就是"确定"的含义。

四、Floyd:三行代码的多点对最短路

for (int k = 1; k <= n; k++)            // k 必须最外层:只允许经过前 k 个点
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            f[i][j] = min(f[i][j], f[i][k] + f[k][j]);

适用:n ≤ 400 左右、需要任意两点间最短路,或作为"建图小工具"(先 Floyd 求出关键点两两距离,再在新图上做 状压 DP——"k 个关键点 + 大图"套路的常见第一步)。三重循环顺序 k-i-j 不能换,换了对部分数据会算出非最短路。

五、易错点清单

六、练习

配套小测(5 题,即时判分)