知识点概述
定义
- 给定一张边带权的无向联通图G = (V,E), n = |V|,m=|E|
- 由V中全部顶点和E中n-1条边构成的无向联通子图被称为G的一棵生成树
- 边的权值之和最小的的生成树被称为无向图G的最小生成树
- 最小生成树:Minimum Spanning Tree,即MS
是一棵树
- 无回路
- n个顶点一定有n-1条边
是生成树
- 包含全部顶点
- n-1条边都在图里
边权和最小
- 最小生成树的n-1条边,边权加起来,是所有生成树中最小的
克鲁斯卡尔(Kruskal)算法(时间复杂度O(mlogm))
算法思想(贪心-贪边)
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止
实际操作
- 建立并查集,每个点各自构成一个集合 (并查集用于判断是否成环)
- 把所有边按照权值从小到大排序,依次扫描每条边(x,y, z)
- 若x,y属于同一个集合(x,y已连通),则忽略这条边,继续扫描下一条
- 否则,合并 x,y 所在的集合,并把 z 累加到答案中
- 直到累加过n-1条边,最小生成树形成
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e3+10;
int father[maxn];
int n,m,sum,cnt;
struct node
{
int st;
int ed;
int w;
}edge[maxn];
bool cmp(node x,node y)
{
return x.w<y.w;
}
void init()
{
for(int i=1;i<=n;i++) father[i]=i;
memset(edge,0,sizeof(edge));
}
int find(int x)
{
return x==father[x]?x:father[x]=find(father[x]);
}
void kruskal()
{
sum=0,cnt=0;
for(int i=1;i<=m;++i)
{
int fx=find(edge[i].st);
int fy=find(edge[i].ed);
if(fx==fy) continue;//判断其是否在同一个连通分量
sum+=edge[i].w;
father[fx]=fy;
cnt++;//统计有多少条边,每合并一次就是多一条边,最小生成树n-1条边
if(cnt==n-1) return;
}
}
int main()
{
while(~scanf("%d %d",&m,&n)&&m)
{
init();
for(int i=1;i<=m;i++) scanf("%d %d %d",&edge[i].st,&edge[i].ed,&edge[i].w);
sort(edge+1,edge+m+1,cmp);//对边按照权值进行排序
kruskal();
if(cnt==n-1) printf("%d\n",sum);
else printf("?\n");
}
}
普里姆(Prim)算法(时间复杂度O(n2))
算法思想(贪心-贪点)
在任意时刻,设已经确定属于最小生成树的结点集合为T,剩余节点集合为S,每次找到最小的一条边(x,y,z),满足x∈S,y∈T。即两个端点分别属于S,T的权值最小的边,然后把点x从S中删除加入到集合T中,并把z累加到答案中
实际操作
- 创建一个最小生成树点集,从源点开始入点集
- 每次找出最小生成树点集外,到这个点集内任意一点距离最小的点
- 把找出的点放入最小生成树点集
- 通过used标记组数,标记是否进行过点的迁移
- 每次找到一点后,更新 ans边权和 & mincost数组最小距离
- 详见代码行注释
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1e3+10;
int cost[maxn][maxn];//边权数组
int mincost [maxn];
//mincost[u]表示有已形成的生成树点集出发的与u相连的最小边权
int T,n,m,x,y,ans;
bool used[maxn];//判断点i是否在生成树点集中
int prim()
{
memset(mincost,inf,sizeof(mincost));//初始化,设最小边权为inf
memset(used,false,sizeof(used));//初始化,表示每个点都没有加入生成树点集
mincost[0]=0;//假设以0号点为出发点
ans=0;//初始化统计最小生成树边权和的变量
while(1)
{
int v=-1;//用来找相邻最小边权点
//从不属于生成树点集中的点中选取权值最小的顶点
for(int u=0;u<n;++u)
{
if(!used[u]&&(v==-1||mincost[u]<mincost[v])) v=u;
/*
条件(未加入生成树点集)且(为找到过可用点 或 边权更小)
边权更小的判断中,若不相连,则边权为初始化的无穷大inf
*/
}
if(v==-1) break;//不存在,则表示所有点已包括在生成树点集中
used[v]=true;//第一轮循环是先把出发点加入点集的
ans+=mincost[v];
for(int u=0;u<n;++u) mincost[u]=min(mincost[u],cost[v][u]);
//更新最小边权
/*
第一轮时,与出发点相连的边将mincost由inf更新为cost[0][u]
cost[0][u]中0表示起始位置(出发点),u需要遍历
找的是生成树点集的相连点
所以曾经相连,与最新入生成树点集的点不相连也没关系
依旧可以保持mincost,不相连的为inf
*/
}
return ans;
}
int main()
{
while(~scanf("%d %d",&n,&m)&&n+m);
{
for(int i=0;i<m;i++) scanf("%d %d %d",&x,&y,&cost[x][y]);
printf("%d\n",prim());
}
return 0;
}