#2020寒假集训#最小生成树入门(Minimum Spanning Tree)代码笔记

知识点概述

定义

  • 给定一张边带权的无向联通图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
#include
#include
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
#include
#include
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;
}

你可能感兴趣的:(2020寒假集训,算法)