算法
图论:遍历与最短路
BFS / DFS / Dijkstra / 拓扑排序——图模型几乎覆盖所有「关系」类问题
图基础
图由顶点(V)和边(E)构成。按方向分有向/无向图,按权重分加权/无权图。C++ 中最常用邻接表(vector'<'vector'<'pair'<'int,int'>''>''>')存图。
// Adjacency list (weighted directed graph)
int n; // number of vertices
vector<vector<pair<int,int>>> adj(n); // adj[u] = {(v, weight), ...}
adj[u].push_back({v, w}); // add edge u -> v with weight w图的表示
表示方式空间适合场景
邻接表O(V + E)稀疏图(LeetCode 大多数图题)
邻接矩阵O(V²)稠密图、Floyd-Warshall
边集合O(E)Kruskal 最小生成树
算法复杂度对比
算法时间适用
BFS / DFSO(V + E)连通性、无权最短路、拓扑排序
Dijkstra(堆优化)O((V+E) log V)非负权最短路
Bellman-FordO(VE)含负权边、检测负环
Floyd-WarshallO(V³)全源最短路(稠密图)
Kruskal / PrimO(E log E)最小生成树
BFS & DFS
BFS(广度优先)用队列逐层展开,天然给出无权图最短路。DFS(深度优先)用栈(或递归)深入分支,适合连通性、环检测、拓扑后序。
BFS 适用场景
- 无权图单源最短路(步数/距离)
- 二部图判断(BFS 2-着色)
- 多源 BFS(同时从多个起点扩散)
- 127. 单词接龙、200. 岛屿数量
// BFS: shortest path in unweighted graph (adjacency list)
// Returns dist[] from src; dist[v] == -1 means unreachable
vector<int> bfs(int n, vector<vector<int>>& adj, int src) {
vector<int> dist(n, -1);
queue<int> q;
dist[src] = 0;
q.push(src);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : adj[u]) {
if (dist[v] == -1) {
dist[v] = dist[u] + 1;
q.push(v);
}
}
}
return dist;
}DFS 适用场景
- 连通分量计数
- 有向图环检测(白/灰/黑三色标记)
- 拓扑排序的后序 DFS
- 207. 课程表、133. 克隆图
// DFS: count connected components (undirected graph)
void dfs(int u, vector<vector<int>>& adj, vector<bool>& vis) {
vis[u] = true;
for (int v : adj[u])
if (!vis[v]) dfs(v, adj, vis);
}
int countComponents(int n, vector<vector<int>>& adj) {
vector<bool> vis(n, false);
int cnt = 0;
for (int i = 0; i < n; i++)
if (!vis[i]) { dfs(i, adj, vis); cnt++; }
return cnt;
}visited 的位置:BFS 中入队时立即标记 visited(防止重复入队);DFS 中进入前标记(或用 path 集合记录当前递归路径以检测环)。
练习:图 — BFS 最短路 — 代码练习
测试用例(3 个)
用例 1
输入:
5 5
0 1
1 2
2 3
3 4
0 3期望:
2
用例 2
输入:
4 3
0 1
1 2
2 3期望:
3
用例 3
输入:
3 1
0 1期望:
-1
拓扑排序
拓扑排序适用于有向无环图(DAG),给出一个所有「依赖在被依赖之前」的线性顺序。两种实现:
两种实现
方法原理特点
Kahn(BFS)维护入度,反复取入度为 0 的节点可检测环(最终队列是否排尽所有节点)
DFS 后序DFS 结束后压栈,栈顶即拓扑首代码简洁,天然递归
// Kahn's topological sort (BFS, detects cycles)
// Returns empty vector if graph has a cycle
vector<int> topoSort(int n, vector<vector<int>>& adj) {
vector<int> indegree(n, 0);
for (int u = 0; u < n; u++)
for (int v : adj[u]) indegree[v]++;
queue<int> q;
for (int i = 0; i < n; i++)
if (indegree[i] == 0) q.push(i);
vector<int> order;
while (!q.empty()) {
int u = q.front(); q.pop();
order.push_back(u);
for (int v : adj[u])
if (--indegree[v] == 0) q.push(v);
}
return (int)order.size() == n ? order : vector<int>{}; // empty = cycle
}典型题目
- 207. 课程表(判断是否有环)
- 210. 课程表 II(输出拓扑顺序)
- 1462. 课程表 IV(拓扑 + 可达性)
环检测:Kahn 算法结束后若仍有节点未被移除,则图中存在环。DFS 中若遇到「灰色」(当前路径上)节点则有环。
练习:图 — 拓扑排序 — 代码练习
测试用例(3 个)
用例 1
输入:
4 4
0 1
0 2
1 3
2 3期望:
0 1 2 3
用例 2
输入:
3 3
0 1
1 2
2 0期望:
cycle
用例 3
输入:
2 1
1 0期望:
1 0
Dijkstra 最短路
Dijkstra 适用于非负权图,贪心地从距离最小的节点扩展。堆优化版时间 O((V+E) log V)。
算法步骤
- 初始化 dist[src] = 0,其余 = INF,将 (0, src) 入最小堆
- 每次弹出堆顶 (d, u);若 d > dist[u] 则跳过(旧数据)
- 遍历 u 的所有邻居 v,若 dist[u] + w < dist[v] 则松弛并入堆
- 所有节点处理完毕,dist[] 即为各顶点最短距离
// Dijkstra with min-heap (adjacency list of (neighbor, weight))
// Returns dist[] from src
vector<long long> dijkstra(int n, vector<vector<pair<int,int>>>& adj, int src) {
const long long INF = 1e18;
vector<long long> dist(n, INF);
priority_queue<pair<long long,int>,
vector<pair<long long,int>>,
greater<>> pq;
dist[src] = 0;
pq.push({0, src});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue; // stale entry
for (auto [v, w] : adj[u]) {
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
return dist;
}典型题目
- 743. 网络延迟时间
- 1631. 最小体力消耗路径
- 787. K 站中转内最便宜的航班(改用 Bellman-Ford)
负权边:Dijkstra 不能处理负权边(贪心前提:已确定最短距离的节点不会被更新)。有负权边用 Bellman-Ford 或 SPFA。
练习:图 — Dijkstra — 代码练习
测试用例(3 个)
用例 1
输入:
4 5
0 1 1
0 2 4
1 2 2
1 3 5
2 3 1期望:
4
用例 2
输入:
3 2
0 1 10
1 2 5期望:
15
用例 3
输入:
3 1
0 1 3期望:
-1
练习:图 — 岛屿数量 (DFS/BFS) — 代码练习
测试用例(3 个)
用例 1
输入:
4 5
11110
11010
11000
00000期望:
1
用例 2
输入:
4 5
11000
11000
00100
00011期望:
3
用例 3
输入:
1 1
1期望:
1