最小生成树(MST)
本文同步发于知乎
定义
原图:\(G=(V,E)\)
生成子图:\(G'=(V,E')\)
树:\(N\) 个点,\(N-1\) 条边的无向连通图
最小生成树:(生成子图+树)边权和最小
Prim(稠密图)
算法
- 初始边集 \(E=\emptyset\),点集 \(V=\{任意一个点\}\)
- 选择一条权值 \(w\) 最小的边 \(e=(u,v)\),满足 \(u\in V, v\notin V\)
- \(E=E+{e},V=V+{v}\)
- 点集 \(V\) 包含所有节点则算法结束,否则返回第二步
时间复杂度
\(\Theta(V^2)\)
使用 优先队列 加速 过程 2,时间复杂度: \(\Theta(V\log V+E\log V)=\Theta(E\log V)\);
使用 斐波那契堆,可进一步优化到 \(\Theta(E+V\log V)\)。
证明
- 令 Prim 算法得到的树为 \(P\),有一棵最小生成树 \(T\),假设他们不同
- 假设前 \(k-1\) 步 \(P\) 选择的边都在 \(T\) 中,令此时的树为 \(P'\)
- 第 \(k\) 步选择的 \(e=(u,v)\) 不在 \(T\) 中,假设 \(u\) 在 \(P'\) 中,而 \(v\) 不在
- \(T\) 中必有一条 \(u\rightarrow v\) 的路径,路径上必有一条边 \(e'=(x,y)\) 满足此时 \(x\) 在 \(P'\) 中而 \(y\) 不在
- 若 \(w(e')>w(e)\) 则在 \(T\) 中用 \(e\) 换掉 \(e'\) 可得到一个更小的生成树,矛盾
- 若 \(w(e')<w(e)\) 则第 \(k\) 步时选的是 \(e'\) 而不是 \(e\),矛盾
- 若 \(w(e')=w(e)\),在 \(T\) 中用 \(e\) 换掉 \(e'\),则 \(P\) 前 \(k\) 步中选择边都在 \(T\) 中
- 有限步后可把 \(T\) 变为 \(P\) 且权值不变,因此 \(P\) 就是最小生成树
代码
const int inf = 0x3f3f3f3f;
const int N = 110;
int n;
int G[N][N];
int dis[N];
bool vis[N];
int weight;
void prim() {
fill_n(dis, N, inf);
dis[1] = 0;
for (int i = 1; i <= n; i++) {
int mi = 0; //注意: dis[0] == inf
for (int j = 1; j <= n; j++)
if (!vis[j] && dis[i] < dis[mi])
mi = i;
if (mi == 0) {
error("不连通");
break;
}
vis[mi] = true;
weight += d[mi];
for (int j = 1; j <= n; j++)
if (!vis[j] && G[mi][j] < dis[j])
dis[j] = G[mi][j];
}
}
Kruskal(稀疏图)
算法
- 将所有边按权值 \(w(e)\) 的大小排序
- 初始选择边集 \(E=\emptyset\)
- 按顺序考虑每条边 \(e\),\(e\) 与已在 \(E\) 中的边不构成环则可选择。\(E=E+{e}\) 若构成环则放弃 \(e\)
- 选出 \(n-1\) 条边后 \(E\) 即为一棵最小生成树,否则原图不连通
使用 并查集 维护 过程 3 中的选择
时间复杂度
边排序 \(\Theta(E\log E)\)
并查集 \(\Theta(E\alpha)\)
总复杂度 \(\Theta(E\log E)\)
证明
- 令 Kruskal 算法得到的树为 \(K\),有一棵最小生成树 \(T\),假设他们不同。
找到边权最小的在 \(K\) 但不在 \(T\) 中的边 \(e\) - 把 \(e\) 加入 \(T\) 中,形成一个环,删掉这个环中一条不在 \(K\) 中的边 \(e'\),得到新生成树 \(T'\)
- 若不存在 \(e'\) 则 \(K\) 存在环,矛盾
- 若 \(w(e')>w(e)\),则 \(T'\) 权值和小于 \(T\),矛盾
- 若 \(w(e')<w(e)\),则 Kruskal 执行时先考虑了 \(e'\),由于成环没加入 \(e'\),因此在 \(e'\) 之前加入的边权值均 \(\le w(e')<w(e)\)。由 \(e\) 的定义,\(K\) 中边权小于 \(w(e)\) 的边均在 \(T\) 中,说明 \(T\) 中边与 \(e'\) 会成环,矛盾
- \(w(e')=w(e)\),在 \(T\) 中用 \(e\) 换掉 \(e'\)
- 有限步后可把 \(T\) 变为 \(K\) 且权值不变,因此 \(K\) 就是最小生成树
代码
struct Edge {
int u, v, w;
bool operator < (const Edge& edge) const {
return w < edge.w;
}
} edges[N], mstEdges[N];
int n, ec, mstec, id[N], weight;
int find(int p) {
if (id[p] == p) return p;
return id[p] = find(id[p]);
}
void kruskal() {
msetc = weight = 0;
sort(edges, edges + ec);
for (int i = 1; i <= n; i++) id[i] = i;
for (int i = 0; i < ec; i++) {
int p = find(edges[i].u), q = find(edges[i].v);
if (p == q) continue;
mstEdges[mstec++] = edges[i];
weight += edges[i].w;
id[p] = q;
if (mstec == n - 1) break;
}
}

浙公网安备 33010602011771号