最小生成树
文章目录
最小生成树
给定一个无向图,如果它的某个子图中任意两个顶点都互相连通且是一棵树,那么这棵树就是生成树 其中,边权和最小的生成树就是最小生成树(MST)
一些性质:
 n-1条边
若T1,T2都是最小生成树,则T1,T2的各边权是相同的(可能连接的边不同),换句话说,若边权各不相同,则最小生成树唯一(实际上也是拟阵贪心的性质)
往生成树上加一条边就形成一个环,假设是(u,v)加边,那么就形成(u,…,lca(u,v),…,v,u)这个环
基本算法
Prim
与Dijkstra比较像 Dijkstra是什么(大雾
 我们先在某一个集合中搞一个点
 定义d[i]表示点i到这个集合的最小距离
 然后不断往这个集合里添加离这个集合最近的点,直到所有的点都加入了这个集合。
 每一次加入一个点 都要去搞一下这个点的相邻的点 来更新d[i]
感觉一下正确性显然(什么鬼操作
 证明 略~~(其实讨厌这个字~~
 详见白书106页
int prim()
{
	d[0]=0;
	int ans=0;
	while(1)
	{
		int u=-1;
		for(int v=0;v<=n;v++)
			if(!vis[v]&&(u==-1||d[v]<d[u]))
				u=v;
		if(u==-1) break;
		vis[u]=1;
		ans+=d[u];
		for(int v=0;v<=n;v++)
			d[v]=min(d[v],c[u][v]);
	}
	return ans;
}
 
同Dijkstra一样,Prim也可以用堆优化 mark一下
 但是我感觉优化效果不明显(可能要在一些特殊数据下或者数据较大的时候吧
Kruskal
按照边的权值从小到大依次check一下,如果不产生环,就把这条边加进去,直到联通所有的点(根据树的性质,则为有n-1条边时
 判断是否产生环,主要是看这条边的两个端点是否已经连通
 就需要并查集来维护
void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]!=x)
		return f[x]=Find(f[x]);
	return f[x];
}
bool Union(int x,int y)
{
	int u=Find(x),v=Find(y);
	if(u==v) return 0;
	if(u>v) f[u]=v;
	else f[v]=u;
	return 1;
}
int Kruskal()
{
	Init();
	int num=0,ans=0;
	for(int i=1;i<=m;i++)
		if(Union(edge[i].u,edge[i].v))
		{
			num++;
			ans+=edge[i].w;
			if(num==n-1) break;
		}
	if(num!=n-1) return -1;//没有生成树 
	return ans;
}
 
考虑到边一般是u,v,w三元组,交换两个结构体的时间开销比较大,所以我们也可以对下标搞排序,就只用交换1个变量就可以啦
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 110
#define MAXM 5010
struct node{
	int u,v,w;
}edge[MAXM];
int n,m;
int f[MAXN],order[MAXM];
void st()
{
	for(int i=1;i<=m-1;i++)
		for(int j=1;j<=m-i-1;j++)
			if(edge[order[j]].w>edge[order[j+1]].w)
			{
				int temp=order[j];
				order[j]=order[j+1];
				order[j+1]=temp;
			}
		
}
void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]!=x)
		return f[x]=Find(f[x]);
	return f[x];
}
bool Union(int x,int y)
{
	int u=Find(x),v=Find(y);
	if(u==v) return 0;
	if(u>v) f[u]=v;
	else f[v]=u;
	return 1;
}
int Kruskal()
{
	Init();
	int num=0,ans=0;
	for(int i=1;i<=m;i++)
		if(Union(edge[order[i]].u,edge[order[i]].v))
		{
			num++;
			ans+=edge[order[i]].w;
			if(num==n-1) break;
		}
	return ans;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		m=0;
		for(int i=1;i<=n*(n-1)/2;i++)
		{
			int uu,vv,ww;
			scanf("%d %d %d",&uu,&vv,&ww);
			edge[++m].u=uu,edge[m].v=vv,edge[m].w=ww,order[m]=i;
		}
		st();
		printf("%d\n",Kruskal());
	}
}//这个代码好像没有调出来qwq
 
Prim VS Kruskal
我感觉更喜欢Kruskal一点
 也不知道为什么 它看起来比较快一点
 
(这两个都是打的单纯的未优化版的板子)
总的来说
 他们都是基于贪心的思想
 Prim与点的关系比较大,适用于稠密图
 Kruskal与边的关系比较大,适用于稀疏图
拟阵
这个要展开的话就非常冗杂了
 所以大概简单说一下
 另外 更深入了解的话 mark一下 https://blog.csdn.net/qingyingliu/article/details/82055737
必选某些边的最小生成树,就可以把那些必选的边先搞进去,再开始算法。
例题
先上板子
HDU 1233 还是畅通工程
某省调查乡村交通状况,得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可),并要求铺设的公路总长度为最小。请计算最小的公路总长度。
测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( < 100 );随后的N(N-1)/2行对应村庄间的距离,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间的距离。为简单起见,村庄从1到N编号。
 当N为0时,输入结束,该用例不被处理。
对每个测试用例,在1行里输出最小的公路总长度。
 Sample Input
 3
 1 2 1
 1 3 2
 2 3 4
 4
 1 2 1
 1 3 4
 1 4 1
 2 3 3
 2 4 2
 3 4 5
 0
 Sample Output
 3
 5
Prim
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define MAXN 2005
#define INF 0x3f3f3f3f
int c[MAXN][MAXN],d[MAXN];
bool vis[MAXN];
int n,m;
int prim()
{
	d[1]=0;
	int ans=0;
	while(1)
	{
		int u=-1;
		for(int v=1;v<=n;v++)
			if(!vis[v]&&(u==-1||d[v]<d[u]))
				u=v;
		if(u==-1) break;
		vis[u]=1;
		ans+=d[u];
		for(int v=1;v<=n;v++)
			d[v]=min(d[v],c[u][v]);
	}
	return ans;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		memset(d,INF,sizeof(d));
		memset(c,INF,sizeof(c));
		memset(vis,0,sizeof(vis));
		m=0;
		for(int i=1;i<=n*(n-1)/2;i++)
		{
			int uu,vv,ww;
			scanf("%d %d %d",&uu,&vv,&ww);
			c[uu][vv]=c[vv][uu]=ww;
		}
		printf("%d\n",prim());
	}
	return 0;
}
 
Kruskal
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 110
#define MAXM 5010
struct node{
	int u,v,w;
}edge[MAXM];
int n,m;
int f[MAXN];
bool cmp(node p,node q)
{
	return p.w<q.w;
}
void Init()
{
	for(int i=1;i<=n;i++)
		f[i]=i;
}
int Find(int x)
{
	if(f[x]!=x)
		return f[x]=Find(f[x]);
	return f[x];
}
bool Union(int x,int y)
{
	int u=Find(x),v=Find(y);
	if(u==v) return 0;
	if(u>v) f[u]=v;
	else f[v]=u;
	return 1;
}
int Kruskal()
{
	Init();
	int num=0,ans=0;
	for(int i=1;i<=m;i++)
		if(Union(edge[i].u,edge[i].v))
		{
			num++;
			ans+=edge[i].w;
			if(num==n-1) break;
		}
	return ans;
}
int main()
{
	while(scanf("%d",&n)!=EOF&&n)
	{
		m=0;
		for(int i=1;i<=n*(n-1)/2;i++)
		{
			int uu,vv,ww;
			scanf("%d %d %d",&uu,&vv,&ww);
			edge[++m].u=uu,edge[m].v=vv,edge[m].w=ww;
		}
		sort(edge+1,edge+m+1,cmp);
		printf("%d\n",Kruskal());
	}
}
 
建图比较巧妙的题目
BZOJ 1601
BZOJ 3714
比较暴力的方式求解生成树问题的变式
数据范围较小
 一般枚举一个什么东西(比方说 生成树上的边,所有边,某一权值

                
            
        
浙公网安备 33010602011771号