一、最小生成树(MST)
1.1 定义
连通无向图中,连接所有顶点的边权和最小的树。
1.2 性质
- 包含 ( V ) 个顶点和 ( V-1 ) 条边
- 无环
- 可能不唯一
1.3 切割定理
任取一个切割, crossing edge(横切边)中权值最小的边一定属于某棵MST。
二、Prim算法
2.1 核心思想
从某个顶点开始,逐步扩展树,每次添加连接树与非树顶点的最小权边。
2.2 实现(优先队列)
public int prim(List<int[]>[] adj) { int n = adj.length; boolean[] inMST = new boolean[n]; PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1])); pq.offer(new int[]{0, 0}); int totalWeight = 0; int edges = 0; while (!pq.isEmpty() && edges < n) { int[] curr = pq.poll(); int u = curr[0], w = curr[1]; if (inMST[u]) continue; inMST[u] = true; totalWeight += w; edges++; for (int[] edge : adj[u]) { int v = edge[0], weight = edge[1]; if (!inMST[v]) { pq.offer(new int[]{v, weight}); } } } return totalWeight; }
|
2.3 复杂度
( O(E \log V) )
三、Kruskal算法
3.1 核心思想
按边权从小到大排序,依次选择不形成环的边,直到选出 ( V-1 ) 条。
3.2 并查集
class UnionFind { int[] parent; UnionFind(int n) { parent = new int[n]; for (int i = 0; i < n; i++) parent[i] = i; } int find(int x) { if (parent[x] != x) parent[x] = find(parent[x]); return parent[x]; } boolean union(int x, int y) { int px = find(x), py = find(y); if (px == py) return false; parent[px] = py; return true; } }
|
3.3 实现
public int kruskal(int[][] edges, int n) { Arrays.sort(edges, Comparator.comparingInt(e -> e[2])); UnionFind uf = new UnionFind(n); int totalWeight = 0; int edgesUsed = 0; for (int[] edge : edges) { if (uf.union(edge[0], edge[1])) { totalWeight += edge[2]; if (++edgesUsed == n - 1) break; } } return totalWeight; }
|
3.4 复杂度
排序 ( O(E \log E) ),并查集 ( O(E \alpha(V)) ),总计 ( O(E \log E) )。
四、对比
| 特性 |
Prim |
Kruskal |
| 贪心策略 |
扩展顶点 |
选最小边 |
| 数据结构 |
优先队列 |
并查集 |
| 适用 |
稠密图 |
稀疏图 |
| 时间 |
( O(E \log V) ) |
( O(E \log E) ) |
五、总结
Prim从顶点出发逐步扩展,适合稠密图。Kruskal从边出发,适合稀疏图。两者都基于切割定理的贪心策略,是图论中最重要的算法之一。