什么是最小生成树?
生成树:一个连通图含有图中全部 n n n个顶点,但只有足以构成一棵树的 n − 1 n-1 n−1条边。一颗有 n n n个顶点的生成树有且仅有 n − 1 n-1 n−1条边,如果生成树中再添加一条边,则必成环。
最小生成树:在连通的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
如何求到最小生成树?
① Prim算法
原理:从起始顶点出发,选择当前可用的最小权值边,把对应的顶点加入到当前建立的生成树中来。
令初始状态为
w
w
w,所有顶点的结合为
V
V
V,当前剩余没用的顶点集合为
v
v
v
(1) 初始化:
u
=
s
,
v
=
V
−
u
u={s},v=V-u
u=s,v=V−u
(2) 找出一条连接集合
u
u
u和
v
v
v的最小代价的边
(
u
0
,
v
0
)
(u0,v0)
(u0,v0),把它连起来,并将
v
0
v0
v0加入到
u
u
u中,并修改相关权值(集合
u
u
u和
v
v
v的最小代价边)。
(3) 重复操作2,直到所有
n
n
n个顶点都被连上为止。
Prim算法过程
代码
代码与最短路中的Dijkstra有点像
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int M=10005;
int G[M][M];
int dis[M],m,n;
bool flag[M];
int Prim(){
memset(dis,0x3f,sizeof(dis));
memset(flag,false,sizeof(flag));
dis[1]=0;
int ans=0;
for(int i=1;i<=n;i++){
int id=0;
for(int j=1;j<=n;j++){
if(!flag[j]&&(id==0||dis[id]>dis[j])){
id=j;
}
}
flag[id]=true;
ans+=dis[id];
for(int j=1;j<=n;j++){
dis[j]=min(dis[j],G[id][j]);
}
}
return ans;
}
int main(){
memset(G,0x3f,sizeof(G));
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
int s,t,w;
scanf("%d %d %d",&s,&t,&w);
G[s][t]=G[t][s]=w;
}
printf("%d",Prim());
}
② Kruskal算法
原理:从边出发,初始状态下最小生成树选取 0 0 0条边,在计算的过程中,每次添加一条满足条件的最小代价的边进入最小生成树中,直到选取 n − 1 n-1 n−1条边为止。
步骤:
(1) 将所有的边按照代价从小到大的顺序进行排序。
(2) 把图中的
n
n
n个顶点看做是独立的
n
n
n棵树。
(3) 按照权值从小到大的顺序进行选择,如果连续的两端不属于同一棵树,那么就选取这条线,否则,就不选取并继续查找。
(4) 重复操作3,直到选取了满足条件的
n
−
1
n-1
n−1条边为止。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int M=100005;
struct edge{
int u,v,w;
}G[M];
int f[M],n,m;
bool cmp(edge x,edge y){
return x.w<y.w;
}
int get(int x){
if(f[x]==x) return x;
return f[x]=get(f[x]);
}
int Kruskal(){
sort(G+1,G+1+m,cmp);
for(int i=1;i<=n;i++){
f[i]=i;
}
int ans=0;
for(int i=1;i<=m;i++){
int x=get(G[i].u),y=get(G[i].v);
if(x==y) continue;
f[x]=y;
ans+=G[i].w;
}
return ans;
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d %d %d",&G[i].u,&G[i].v,&G[i].w);
}
printf("%d",Kruskal());
}