其他分享
首页 > 其他分享> > 最小生成树

最小生成树

作者:互联网

最小生成树主要应用:

举个例子,两个城市需要光缆联通,且两个城市安装光缆有一定价格,任意两个城市必须联通,求最小价格

这时候就需要运用到最小生成树,当然这个题只是需要套模板,有些变种:https://www.luogu.com.cn/problem/P1195  这道题也是最小生成树,换汤不换药

最小生成树有2种算法:prim和Kruskal

Kruskal:

这个相对比较容易理解,就是将所有边取出来放入一个列表,并且放入时要排序从小到大(用贪心的思想优先选取权值较小的边)。在这之后,就往图中填边。在填边的过程中注意,如果他构成了一个环,舍弃这条边(用并查集来判断是否存在环)(因为如果构成环肯定已经联通,再加入就是多余的),否则就计数器++(计数器是记录填入边的数量),如果到n-1就终止循环,过程中再ans(=0)再加上填入边的权值就可以了QAQ

上图为Kruskal的图,将左边列表依次加入就行了!

#include<bits/stdc++.h>	//万能头

using namespace std;

int n , m ,fa[50005],cnt,ans,flag;//n个点,m条边,并查集判断环 ,cnt为加入边数 
struct node{
	int u,v,w;
}edge[200005];//意义不明的存边 

bool cmp(node a,node b)
{
	return a.w < b.w;
}//排序依据,从小到大 

int find(int x)
{
	if(fa[x] == x)
		return x;
	else 
		return fa[x] = find(fa[x]);//递归+状态压缩 实现并查集的‘查’ 
}

void merg(int a,int b)
{
	int x = find(a),y = find(b);
	if(x==y)
		return;
	else
		fa[x] = y;//合并函数 
}

int main()
{
	cin >> n >> m;
	for(int i = 1 ; i <= n ; i ++)
		fa[i] = i;//初始化并查集 
	for(int i = 1 ; i <= m ;  i++)
		cin >>edge[i].u >> edge[i].v >> edge[i].w;//虽然无向图但不影响 
	sort(edge+1,edge+m+1,cmp);
	for(int i = 1 ; i <= m ;  i++)
	{
		int a = edge[i].u,b = edge[i].v,c = edge[i].w;
		if(cnt == n-1)
		{
			break;//如果已经加入了n-1条边,直接跳出 
		}
		if(find(a)==find(b))
			continue;//如果已经联通跳出进行下一次;
		ans += c;
		cnt++;
		merg(a,b);
	}
	if(cnt!=n-1)//判断是否不连通 
		cout<<"orz";
	else
		cout<<ans;

} 

 

 亲自手打代码+详细注释

prim:

所谓prim和最短路的dijkstra非常接近,只是目的不同,首先以0为起点,遍历起点为0的边,如果边的v(初始全部为inf)要小于从起点距离+权值就替换,类似蓝白点,标记0,再选一个最小的作为起点

1.更新(以起点为出发点,更新所连接点的距离(初始为正无穷inf))

2.扫描(扫描距离最小的点并且未被标记,将其作为下一次的起点)

3.创建(以扫描到的起点为出发点,重复上述操作,直到覆盖完全部的点)

如果还是看不懂推荐去看这个视频:https://www.bilibili.com/video/BV1Eb41177d1?spm_id_from=333.337.search-card.all.click&vd_source=5f02dc5b35066015d1aef125eacf7273

接下来代码贴上:

#include <bits/stdc++.h>//万能头 
#define inf 0x3f3f3f

using namespace std;

int n,m,cnt,ans,dis[400005],vit[400005],now = 1,tot,minn,head[400005],flag;
struct edge{
	int v,w,nxt;
}e[400005];

void add(int u,int v,int w)
{
	e[++cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = head[u];
	head[u] = cnt;
} //链式前向星存图 ,方便后面遍历边 
 
void prim()//噩梦开始的地方 
{
	for(int i = 2 ; i <= n ; i++)
		dis[i] = inf;//将除了起点之外的点的距离初始化为正无穷 
	for(int i = head[1];i;i = e[i].nxt)
	{
		dis[e[i].v] = min(dis[e[i].v],e[i].w);
	}//遍历起点1能到达的点 
	while(++tot<n)//for循环也可以吧 
	{
		minn = inf;//找最小值,赋为最大 
		vit[now] = 1;//将现在的点标记一下,也就是起点 
		for(int i = 1 ; i <= n ; i ++)
		{
			if(!vit[i]&&minn>dis[i])
			{
				minn=dis[i];
				now = i;
			}	
		}//找没被标记的最小值 
		if(vit[now])
		{
			flag = 1;
			return;
		}//如果还是被标记了说明压根没找到,图不连通,返回		 
		ans += minn;//加上到这个点的边的长度 
		for(int i = head[now];i;i = e[i].nxt)
		{
			if(!vit[e[i].v]&&dis[e[i].v]>e[i].w)
			{
				dis[e[i].v] = e[i].w;
			}
		}//从这个起点更新其他未被标记点 
	}
 } 


int main()
{
	cin >> n >> m;//n个点,m条边
	for(int i = 1; i <= m ; i ++)
	{
		int x,y,z;
		cin >> x >> y >> z;
		add(x,y,z);
		add(y,x,z);
	}//无向图,建图 
	prim();
	if(flag)	
	{
		cout<<"orz";
		return 0;
	}//无联通 
	
	
	printf("%d", ans);
	return 0;
} 

  

 prim多用于稠密图,Kruskal多用于稀疏图,比赛时看清数据范围

最后:

 

标签:cnt,prim,int,最小,生成,edge,return,起点
来源: https://www.cnblogs.com/fk-thank/p/16629061.html