图论基础

存图、最短路、并查集、拓扑、生成树

存图

邻接表(推荐,稀疏图):

vector<vector<int>> adj(n);
adj[u].push_back(v);  // 有向边 u->v
// 无向:adj[v].push_back(u);

带权边可存 pair<int,int>(邻接点, 权值)。

链式前向星head[]to[]nxt[],边多时常数更小。

并查集 Union-Find

维护连通性、Kruskal 最小生成树、离线询问:

vector<int> fa(n);
iota(fa.begin(), fa.end(), 0);
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void unite(int x, int y) {
    x = find(x); y = find(y);
    if (x != y) fa[x] = y;  // 或按 size 合并
}

路径压缩 + 按秩 / 按大小合并,均摊接近 $O(1)$。

最短路

算法 复杂度 适用
BFS $O(V+E)$ 边权为 1
Dijkstra $O((V+E)\log V)$ 非负权,单源
Bellman-Ford $O(VE)$ 可有负权、判负环
SPFA 均摊较快,最坏 $O(VE)$ 负权,易被卡
Floyd $O(V^3)$ 全源,$V \le 500$

Dijkstra(堆优化):

priority_queue<pair<ll,int>, vector<...>, greater<...>> pq;
dist[s] = 0; pq.push({0, s});
while (!pq.empty()) {
    auto [d, u] = pq.top(); pq.pop();
    if (d != dist[u]) continue;
    for (auto [v, w] : adj[u])
        if (dist[u] + w < dist[v]) {
            dist[v] = dist[u] + w;
            pq.push({dist[v], v});
        }
}

负环:Bellman-Ford 松弛 $n$ 轮后仍能松弛则存在负环。

拓扑排序

DAG 上按依赖顺序排列,用于课程表、DP 顺序、判环。

Kahn:入度为 0 入队,弹出时减邻点入度;若出队数 $< n$ 则有环。

DFSvis 三色(0 未访问 / 1 访问中 / 2 完成),后序逆序即为拓扑序;遇到 1 说明有环。

最小生成树 MST

Kruskal:边按权排序,用并查集依次加边,不形成环。复杂度 $O(E \log E)$。

Prim:类似 Dijkstra,维护到当前生成树的最小边权,稠密图可用 $O(V^2)$ 朴素版。

欧拉路径 / 回路

无向图:所有点度数为偶数(欧拉回路)或恰有两个奇度点(欧拉路径,从奇度点出发)。有向图看入度出度。DFS 删边 Hierholzer 算法构造。

强连通分量 SCC

有向图缩点:Tarjan 或 Kosaraju,$O(V+E)$。用于 2-SAT、DAG 上 DP 的缩点层。

例题

例 1:最短路(堆优化 Dijkstra)
$n$ 个点 $m$ 条有向边,边权非负,求 $1$ 号点到各点最短路。邻接表 + 小根堆,弹出时若 d != dist[u] 则跳过过期状态。复杂度 $O((n+m)\log n)$。

例 2:课程表 / 拓扑排序
$n$ 门课、先修关系 $m$ 条边 $u \to v$(学完 $u$ 才能学 $v$)。Kahn 入度为 0 入队;若最终出队数 $< n$ 则存在环,无法完成所有课。否则出队顺序即一种合法选课顺序。