最小生成树模板 || 理解

 两个经常忘记的问题

1)过程如何保证图连通

2)为什么是贪心算法而不是动态规划

参考自

笔记:边多prim,点多kruskal

prim算法:

理解:

初始化:设置所有点的dis[i]无限大,更新从起点能达到的所有点的距离dis[i],并设置起点已经被访问。

过程:每次寻找dis最小且未被访问的点,并将这个dis值加到sum,更新从当前起点能达到的所有点的距离dis[i],最终sum的值就是最小生成树的值。

问题(1)过程如何保证图连通(2)为什么是贪心算法。更新从当前起点能达到的所有点的距离dis[i],这一步其实也保存了当前起点能达到的所有点,可能当前起点只会选择一个点到达,或者一个都不选。是按照dis最小来选择的,也就是说从访问过的点,能达到的所有点中挑一个花费最小的(这一步保证了图连通,也说明了为什么是贪心而不是动规,备注),作为下一次的当前起点

备注:图连通这一过程不具备“传递”这一特点,不具备动态规划的各种特点,所以为贪心。Prim算法能记录路径。

代码:

int prim(int start)
{
	int i,j,now,sum=0;
	for(i=1;i<=n;i++){//初始化 
		dis[i]=max1;
		vis[i]=false;
	}
	for(i=1;i<=n;i++){//先选定1为起点,
		dis[i]=graph[1][i]; 
	}
	dis[1]=0;
	vis[1]=true;
	for(i=1;i<n;i++){//循环n-1次数,寻找最小边 
		now=max1;
		int min1=max;
		for(j=1;j<=n;j++){//寻找最小边
			if(vis[j]==false&&dis[j]<min1){
				now=j;
				min1=dis[j];
			}
		}
		if(now==max1) break;//如果不成图,
		vis[now]=true;
		sum+=min1;
		for(j=1;j<=n;j++){//添加新的结点后更新距离
			if(vis[j]==false&&dis[j]>graph[now][j]){
				dis[j]=graph[now][j]; 
			} 
	    }
    }
    if(i<n) printf("????\n");
    else printf("%d\n",sum);
}

kruskal算法:

理解:有了前面Prim算法的理解,Kruskal算法理解起来也会更容易一些。

初始化:首先按照边的值大小进行排序。并查集初始化,所有点指向自己f[i] = i。

过程:每次挑选边最小的,且未加入集合的(就是未访问),当挑选的边达到n-1说明图已经连通了。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm> 
using namespace std;
const int maxn=10010,maxv=110;
int f[maxv];//
struct node{
	int u,v;//边的两个顶点编号 
	int cost;//边权 
}graph[maxn];
bool cmp(node x,node y)//设置为升序 
{
	return x.cost<y.cost;
}
int findfather(int x)//并查集部分 
{
	int a=x;
	while(x!=f[x]) x=f[x];
	while(a!=f[a]){//状态压缩 
		int z=a;
		a=f[a];
		f[z]=x; 
	}
	return x;
}
int kruskal(int n,int m)//kruskal部分 
{
	int ans=0,sumedge=0;//ans为所求边权之和,sumedge为当前生成树的边数
	for(int i=0;i<n;i++) f[i]=i;//并查集初始化
	sort(graph,graph+m,cmp);
	for(int i=0;i<m;i++){
		int fu=findfather(graph[i].u);//查询测试边两个端点所在集合的根结点 
		int fv=findfather(graph[i].v);
		if(fu!=fv){
			f[fu]=fv;//合并集合 
			ans+=graph[i].cost;//边权之和增加测试边的边权 
			sumedge++;//当前生成树的边数加1 
			if(sumedge==n-1) break;//边数等于顶点数减一时结束算法 
		}
	}
	if(sumedge!=n-1) return -1;//无法连通时返回-1 
	return ans;//返回最小生成树的边权之和 
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);//顶点数,边数
	for(int i=0;i<m;i++){
		scanf("%d%d%d",&graph[i].u,&graph[i].v,&graph[i].cost);//两个端点,边权值 
	}
	printf("%d\n",kruskal(n,m));
	return 0;
}
/*
6 10
0 1 4
0 4 1
0 5 2
1 2 1
1 5 3
2 3 6
2 5 5
3 4 5
3 5 4
4 5 3
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值