第 6 章 · 图

图 = 顶点集 + 边集,边可带方向与权值;「多对多」关系。

6.1 邻接矩阵 vs 邻接表

表示结构空间判断边遍历邻居
邻接矩阵V×V 二维数组O(V²)O(1)O(V)
邻接表每个顶点一个链表O(V+E)O(deg)O(deg)
#define MAXV 1000
// 邻接表:V 个顶点的链表头数组,adj[u] 是 u 的出边链表头
typedef struct Edge { int to, w; struct Edge *next; } Edge;
Edge *adj[MAXV];

6.2 DFS / BFS

DFS 沿一条路走到底再回溯(递归或显式栈),BFS 逐层扩散(队列)。

int visited[MAXV];
void dfs(int u) {
    visited[u] = 1;
    for (Edge *e = adj[u]; e; e = e->next)
        if (!visited[e->to]) dfs(e->to);
}

BFS 用队列(第 3 章),首次到达某顶点时的层数就是无权最短路。DFS 产生深度优先森林,用于拓扑排序、强连通分量;BFS 用于最短路径、二分图判定。

6.3 最小生成树:Prim vs Kruskal

连通带权无向图的生成树中,边权和最小的那棵。

// Prim:从点出发,每次把「离当前树最近」的点并入(贪心 + 优先队列,见第 5 章堆)
// Kruskal:把所有边按权排序,用并查集(第 11 章)判断加入是否成环
PrimKruskal
起点顶点
依赖优先队列排序 + 并查集
复杂度O(E log V)O(E log E)
适合稠密图稀疏图

6.4 最短路径:Dijkstra vs Floyd

Dijkstra:单源最短路径,要求边权非负。每次选「当前距离最小」的未确定点,松弛它的邻居,O(E log V)。

// dist[v] 记录源到 v 的最短距离;每次取未确定中最小者,用优先队列优化

Floyd:多源最短路径,三重循环动态规划(第 10 章),允许负权边(无负环),O(V³)。

// dp[k][i][j]:只经过前 k 个点的 i→j 最短路,压掉 k 维
for (int k = 0; k < V; k++)
    for (int i = 0; i < V; i++)
        for (int j = 0; j < V; j++)
            if (d[i][k] + d[k][j] < d[i][j]) d[i][j] = d[i][k] + d[k][j];

对比:矩阵 O(1) 判边但 O(V²) 空间;邻接表 O(V+E) 省空间但判边扫链表。稠密选矩阵,稀疏选表。

对比:两者皆贪心(第 10 章);Prim 宜稠密图,Kruskal 宜稀疏图,多依赖并查集判环。

对比:Dijkstra 单源、需非负权、O(E log V);Floyd 求所有点对、支持负权(无负环)、O(V³)。