最小生成树 MST
最小生成树 MST 算法 分为两种:
- Prim MST
- Kruskal MST
Prim 最小生成树
啥是 Prim MST ???
Prim的思想是将任意一个节点作为根,再更新与之相邻的所有边(用一遍循环即可),再将新的离已存在树最近的节点更新并以此节点作为根继续搜
维护一个数组:dis
,记录已用点到未用点的最短距离。
蒟蒻证明
Prim 算法之所以是正确的,主要基于一个判断:
对于任意一个顶点 \(V\) ,连接到该顶点的所有边中的一条最短边 \((V, V_j)\) 必然属于最小生成树(即 任意一个属于最小生成树的连通子图,从外部连接到该连通子图的所有边中的一条最短边必然属于最小生成树)
蒟蒻代码
// Prim
#include <bits/stdc++.h>
#define re register
using namespace std;
struct Edge{
int to,nxt,val;
};
const int M=2e5+5;
const int N=5e3+5;
int n,m;
int tot=0;
int head[N];
Edge edge[M<<1]; // 无向图开两倍
int dis[N]; // 未更新的点到已更新点的最短距离
int mst=0;
bitset<N> vis; // 是否更新
void add(int x,int y,int z){
edge[++tot].to=y;
edge[tot].val=z;
edge[tot].nxt=head[x];
head[x]=tot;
}
int main()
{
ios::sync_with_stdio(0);
clock_t c1 = clock();
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
// ======================================================================
memset(dis,0x3f,sizeof(dis));
cin>>n>>m;
for(re int i=1;i<=m;i++){
int a,b,c; cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
dis[1]=0;
for(re int i=1;i<=n;i++){
int node=0;
for(re int j=1;j<=n;j++)
if((!vis[j]) && dis[j]<dis[node]) node=j; // 寻找离更新过的点最近的点
vis[node]=1;
for(re int j=head[node]; j; j=edge[j].nxt){
int y=edge[j].to; int w=edge[j].val;
if((!vis[y]) && w<dis[y]) dis[y]=w; // 更新与该点相邻的点
}
}
if(vis.count()<n) puts("orz");
else{
for(re int i=1;i<=n;i++) mst+=dis[i];
cout<<mst<<endl;
}
// ======================================================================
end:
cerr << "Time Used:" << clock() - c1 << "ms" << endl;
return 0;
}
Kruskal 最小生成树
啥是 Kruskal MST ?
先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量为总点数减一即可。
蒟蒻证明
如果某个连通图属于最小生成树,那么所有从外部连接到该连通图的边中的一条最短的边必然属于最小生成树。
所以不难发现,当最小生成树被拆分成彼此独立的若干个连通分量的时候,所有能够连接任意两个连通分量的边中的一条最短边必然属于最小生成树
蒟蒻代码
// Kruskal
#include <bits/stdc++.h>
#define re register
using namespace std;
// 边
struct edge{
int u,v,val; // u->v 边权val
};
const int M=2e5+5;
const int N=5e3+5;
int n,m;
int fa[N];
edge e[M];
int cnt=0; // 加入树中的边数
int mst=0;
// 获取祖宗
int get(int x){
if(fa[x]==x) return x;
return fa[x]=get(fa[x]);
}
// 合并祖宗
void merge(int x,int y){
fa[get(x)]=get(y);
}
bool cmp(const edge& e1, const edge& e2){
return e1.val<e2.val;
}
int main()
{
ios::sync_with_stdio(0);
clock_t c1 = clock();
#ifdef LOCAL
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
#endif
// ======================================================================
cin>>n>>m;
for(re int i=1;i<=n;i++) fa[i]=i; //! 初始化 fa[]!!! QwQ
for(re int i=1;i<=m;i++){
int a,b,c; cin>>a>>b>>c;
e[i].u=a; e[i].v=b; e[i].val=c;
}
sort(e+1,e+1+m,cmp); // 排序
for(re int i=1;i<=m;i++){
int x=get(e[i].u); int y=get(e[i].v);
if(x==y) continue; // 两个点不在同一集合
mst+=e[i].val; // 把边加入 MST
merge(x,y); // 合并集合
if(++cnt==n-1) break; // 由 n 个节点组成的树, 只有 n-1 条边
}
if(cnt!=n-1) puts("orz");
else cout<<mst<<endl;
// ======================================================================
end:
cerr << "Time Used:" << clock() - c1 << "ms" << endl;
return 0;
}