什么是最小生成树
在一张带权无向连通图 $G=(V,E,w)$ 中,最小生成树(Minimum Spanning Tree, MST)是连接所有顶点、且边权和最小的一棵生成树(含 $|V|-1$ 条边、无环).若图不连通,则对应为最小生成森林.
性质
割性质(Cut Property):任取把顶点分成两部分的割,跨越该割的最轻边一定属于某个 MST.
环性质(Cycle Property):任取一个环,其中的最重边不属于任何 MST.
最小瓶颈:任一 MST 同时也是最小瓶颈生成树(最大边权尽可能小).
唯一性:若所有边权互不相同,MST 唯一.
权值平移/缩放:边权统一加同一常数或乘正数,不改变 MST 结构.
允许负边:允许负权,算法与性质不变(只要图无向且可连通).
相关证明
设定 令 $G=(V,E,w)$ 为有限的带权无向连通图,权函数 $w:E\to\mathbb{R}$.生成树 $T\subseteq E$ 指在 $(V,E)$ 上无环且连通、含 $|V|-1$ 条边的子图.记 $\mathrm{MST}(G)$ 为所有使 $\sum_{e\in T}w(e)$ 最小的生成树集合.对任意割 $(S,\bar S)$($S\subset V,\ S\neq\varnothing,\ S\neq V$)称跨越该割且权值最小的边为该割的一条轻边.以下结论均在该设定下成立.
命题 1(割性质)
若 $e$ 是某个割 $(S,\bar S)$ 的轻边,则存在 $T^\star\in\mathrm{MST}(G)$ 使得 $e\in T^\star$.
证明 取任意 $T\in\mathrm{MST}(G)$.若 $e\in T$ 则命题已证.若 $e\notin T$,则 $T\cup\{e\}$ 含唯一简单环 $C$.环 $C$ 必含一条跨越同一割的边 $f\in T$,否则 $C$ 不可能穿越该割回到原侧.由 $e$ 为该割轻边知 $w(e)\le w(f)$.令 $T' = T\cup\{e\}\setminus\{f\}$ 则 $T'$ 为生成树且 $\sum_{T'} w \le \sum_T w$.由 $T$ 的最优性得 $\sum_{T'} w=\sum_T w$,从而 $T'\in\mathrm{MST}(G)$ 且含 $e$.证毕.
命题 2(环性质)
设 $C$ 为图中一环.若 $e\in C$ 满足 $w(e)>\max\{w(g):g\in C\setminus\{e\}\}$(即 $e$ 在 $C$ 上严格最重),则 $e\notin T$ 对一切 $T\in\mathrm{MST}(G)$.
证明 反设存在 $T\in\mathrm{MST}(G)$ 且 $e\in T$.删去 $e$ 后 $T\setminus\{e\}$ 分成两连通块,环 $C\setminus\{e\}$ 中必有某边 $f$ 跨越这两块.由严格最重 $w(f)<w(e)$.令 $T' = T\setminus\{e\}\cup\{f\}$ 得 $\sum_{T'} w < \sum_T w$,与 $T$ 的最优性矛盾.证毕.
命题 3(MST 即最小瓶颈生成树)
设对生成树 $T$ 的“瓶颈”定义为 $\beta(T)=\max_{e\in T}w(e)$.则任意 $T^\star\in\mathrm{MST}(G)$ 皆使 $\beta(T^\star)$ 在所有生成树中取最小值.
证明 取任意 $T^\star\in\mathrm{MST}(G)$,令 $b=\beta(T^\star)$.考虑由所有权值 $<b$ 的边构成的子图,其连通块个数记为 $c$.由于 $T^\star$ 中权值 $<b$ 的边最多把图连成 $c$ 块,剩余为连通必须再选 $c-1$ 条跨块的边,且这些边的权值都$\ge b$.故任一生成树 $T$ 至少含一条权值 $\ge b$ 的边,于是 $\beta(T)\ge b=\beta(T^\star)$.因而 $T^\star$ 瓶颈最小.证毕.
命题 4(唯一性)
若所有边权互不相同,则 $\mathrm{MST}(G)$ 唯一.
证明 反设存在不同的 MST,记为 $T_1\neq T_2$.取对称差 $(T_1\triangle T_2)$ 中权值最小的一条边 $e$.不妨 $e\in T_1\setminus T_2$.将 $e$ 加入 $T_2$ 得一环 $C$,其中必存在 $f\in C\cap T_2$ 且 $f\notin T_1$.由最小性 $w(e)<w(f)$.令 $T_2' = T_2\cup\{e\}\setminus\{f\}$,则 $\sum_{T_2'} w < \sum_{T_2} w$,与 $T_2$ 为最小生成树矛盾.证毕.
命题 5(权值平移/缩放不改变解的结构)
对任意常数 $c\in\mathbb{R}$ 与任意正数 $\alpha>0$,令新权函数
$$w_c(e)=w(e)+c,\qquad w_\alpha(e)=\alpha\,w(e).$$
则 $\mathrm{MST}(V,E,w)=\mathrm{MST}(V,E,w_c)=\mathrm{MST}(V,E,w_\alpha)$.
证明 对任一生成树 $T$,有
$$\sum_{e\in T} w_c(e)=\sum_{e\in T} w(e)+c(|V|-1),\qquad \sum_{e\in T} w_\alpha(e)=\alpha\sum_{e\in T} w(e)$$
前者在不同 $T$ 间仅差一个与 $T$ 无关的常数,后者在不同 $T$ 间做同一正比例缩放,均不改变极小化者的集合.证毕.
命题 6(负权可行)
若允许某些边 $e$ 取负权,所有上述结论与标准算法(如 Kruskal/Prim)的正确性保持不变.
证明 由命题 5,取 $c>-\min_{e\in E} w(e)$ 并考虑 $w_c=w+c$,则 $w_c\ge 0$.对 $(V,E,w_c)$ 的最小生成树与对 $(V,E,w)$ 的最小生成树相同,且割/环性质仅依赖相对比较与交换论证,不依赖权是否为负.于是结论对负权同样成立.证毕.
Kruskal算法
这是Kruskal算法的核心思想
“按权值从小到大选边,能连就连,成环就跳过。”
至于为什么要成环就跳过,因为生成树不能有环,而且一旦出现环,总权值不会变小,只会变大,如果非要证明我要引用一下
给定无向连通图 $G=(V,E,w)$。我们证明“成环的边中,最重的那一条不属于某棵最小生成树(至多在权值并列时可被任意一条同重边替换),因此对按从小到大扫边的 Kruskal 来说,遇到会成环的边直接丢弃不劣;等价地,若为了连通性加入一条边导致出现环,那么把环里的一条更重边删掉,总权值不会变小,只会变小或相等”。证明(交换论证):取任意一条简单环 $C\subseteq E$,令 $e\in C$ 满足 $w(e)\ge w(x)$ 对所有 $x\in C$。若存在一棵最小生成树 $T$ 不含 $e$,结论对这条 $e$ 已证;若所有最小生成树都含 $e$,取其中一棵 $T$ 并考察 $C$。由于 $C$ 是环,$C\setminus\{e\}$ 在图中将 $e$ 的两个端点重新连通;把 $e$ 从 $T$ 中删去得到森林 $T-e$(两连通分量),环上必存在一条边 $f\in C\setminus\{e\}$ 横跨这两个分量(否则环无法闭合)。将 $f$ 加回去得 $T'=(T-e)\cup\{f\}$,它仍是生成树;并且 $w(T')=w(T)-w(e)+w(f)\le w(T)$。若 $w(f)<w(e)$,则 $w(T')<w(T)$,与 $T$ 的最小性矛盾;故只能有 $w(f)=w(e)$,此时 $T'$ 也是最小生成树,且不含 $e$。于是对任意环,其最重边总可以被剔除而不劣(权值严格劣或相等),这叫“环性质”。Kruskal 每次从未选边里取当前最小权值且不成环的边,若某条候选边在当下会与已选边构成环,则它属于某个环且不比环上别的边轻,按上面的结论它不必进入任何最优解,直接跳过不影响可达到的最小总权值。因此 Kruskal 的“成环就跳过”是安全且保持最优的;同理,从一棵树额外加一条边一定形成唯一环,若你坚持保留这条新增边,就必须从该环删去别的边以回到树结构,而为了使总权值不增加,理性选择是删去环中不轻于它的边(最重者),因此“出现环并作合理消解后,总权值不变或变小;若你误把较轻的删了、把较重的留下,总权值只会变大”。综上,生成树本身无环,而在构造过程中“成环即弃”的策略保证了总权值非增,最终得到最小生成树。
算法流程
读入
n
个点、m
条边,边是三元组(u,v,w)
。按
w
升序排序所有边。初始化并查集 DSU:
parent[i]=i
,size[i]=1
,已选边数cnt=0
,答案权值和ans=0
。依次扫描排序后的每条边
(u,v,w)
:做
fu=find(u)
,fv=find(v)
;若
fu!=fv
:把这条边加入生成树:ans+=w
,union(fu,fv)
,cnt++
;若
fu==fv
:跳过(会成环)。
当
cnt==n-1
时停止;输出ans
与边集。若扫完仍
cnt<n-1
,原图不连通,仅得到最小生成森林(或按题意报错/提示)。
这本质上是一个贪心的思想,非常易于理解
#include<bits/stdc++.h>
using namespace std;
struct E{int u,v,w;};
int fa[100005],s[100005];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
bool unite(int a,int b){
a=find(a);b=find(b);
if(a==b)return 0;
if(s[a]<s[b])swap(a,b);
fa[b]=a;s[a]+=s[b];
return 1;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int n,m;cin>>n>>m;
vector<E> e(m);
for(int i=0;i<m;i++)cin>>e[i].u>>e[i].v>>e[i].w;
sort(e.begin(),e.end(),[](E a,E b){return a.w<b.w;});
for(int i=1;i<=n;i++)fa[i]=i,s[i]=1;
long long ans=0;int cnt=0;
for(auto &x:e){
if(unite(x.u,x.v)){
ans+=x.w;
if(++cnt==n-1)break;
}
}
if(cnt<n-1)cout<<"No MST\n";
else cout<<ans<<"\n";
}
Prim算法
核心思想
从任意一个起点开始,把它加入生成树集合 T。
每次选择 T 内的点 与 T 外的点 之间权值最小的边,把这条边和它连接的那个新点加进 T。
重复这个过程,直到所有点都在 T 里。
执行流程
选一个起点 → 入堆 (0, start)
循环直到堆空:
弹出堆顶
(w,u)
如果 u 已访问 → 跳过
否则加入 MST,累加权值
遍历 u 的所有边 (u,v,w) → 如果 v 未访问 → 入堆
(w,v)
直到所有点都加入 MST。
类似 Dijkstra
#include<bits/stdc++.h>
using namespace std;
struct E{int v,w;};
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int n,m;cin>>n>>m;
vector<vector<E>> g(n+1);
for(int i=0;i<m;i++){
int u,v,w;cin>>u>>v>>w;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
vector<int> vis(n+1,0);
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>> pq;
pq.push({0,1});
long long ans=0;int cnt=0;
while(!pq.empty()){
auto [w,u]=pq.top();pq.pop();
if(vis[u])continue;
vis[u]=1;
ans+=w;
cnt++;
for(auto &e:g[u])if(!vis[e.v])pq.push({e.w,e.v});
}
if(cnt==n)cout<<ans<<"\n";
else cout<<"impossible\n";
}