最小生成树——prim算法和kruskal算法

定义

  • 连通网: 在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
  • 生成树: 一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
  • 最小生成树: 在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

接下来以题为例,分别介绍prim和kruskal算法
在这里插入图片描述
关于输入输出要求
输入一个n表示总共的村庄数
接下来输入一个n*n的矩阵表示铺设电话线的路径
(第一行的n个数分别表示a与b,c,d,e,f直接连通的路径,以此类推其他行)

输出就是最小的电话线铺设路径

输入样例

6
0 3 0 5 4 0
3 0 1 0 0 8
0 1 0 3 0 4
5 0 3 0 11 0
4 0 0 11 0 6
0 8 4 0 6 0

输出样例

15

prim算法
假设初始时,只有一个点是连接的,从这个点开始顺次查找连接外面最小花费的边(用于稠密图)

#include<iostream>
#include<cstdio>
using namespace std;
const int INF = 1e8;
int a[55][55]; //a[i][j]表示从i到j的路径长度
int dist[55]; //已连接的点中,到下标index最小的路径
int vis[55];
long long ans;
int main()
{
	int i, j, k, n, mark;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			scanf("%d", &a[i][j]); 
			if (a[i][j] == 0) a[i][j] = INF; //等于0表示不连通,路径为无穷,INF是预设的很大的值
		}
	}
	//下面是prim算法
	dist[1] = 0;      //从V1开始顺次连接,V1到V1,路径为0
	vis[1] = 1;  //vis为1表示已经连接(可以通话了)
	for (i = 2; i <= n; i++)
	{
		dist[i] = a[1][i];  //初始的时候只有1是连接的,所以到i村庄最小的造价就是原数组a[1]的值
	}
	dist[0] = INF; //让dist[0]最大,方便后面找到较小的路径
	for (i = 1; i <= n; i++)
	{
		mark = 0;
		for (j = 1; j <= n; j++) //依次遍历,找到最小的
		{
			if (vis[j]==0&&dist[mark] > dist[j]) mark = j; //标记目前路径最短的村庄
		}
		if (mark == 0) break; //如果mark没变,说明不存在连通的村庄了,退出循环
		ans += dist[mark];  //ans存储总路径,dist[mark]是当前最短的路径,加上去
		vis[mark] = 1;  //第mark个村庄可以通话了
		for (j = 1; j <= n; j++) //因为可以通话的村庄范围变了,需要重新更新dist的值
		{
			if (a[mark][j] < dist[j]) dist[j] = a[mark][j]; //从mark到j的路径如果比原来的小,更新
		}
	}
	printf("%d\n", ans); //输出结果
}

kruskal算法
假设初始时各个点都是单独的孤岛,路径从小到大依次连接连通分支(用于稀疏图)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct Edge {
	int x, y, p; //分别表示边的左右顶点和权值
}E[2222]; 
int fa[55];
int Emax, num; //Emax表示可搭建的边数,num表示已搭建的边数
long long ans;
int find(int x)
{
	if (x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
void unity(int a, int b)
{
	a = find(a);
	b = find(b);
	fa[a] = b;
}
int cmp(const struct Edge& a, const struct Edge& b)
{
	return a.p < b.p;
}

void build(int i, int j, int temp) 
{
	Emax++;
	E[Emax].x = i;
	E[Emax].y = j;
	E[Emax].p = temp;
}
int main()
{
	int i, j, k, n, temp;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		fa[i] = i;
	}
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			scanf("%d", &temp);
			if (temp != 0) build(i, j, temp); //如果有边,则构造这样的边
		}
	}
	sort(E, E + Emax, cmp); //kruskal算法需要按权值从小到大排序
	i = 1;
	while (num < n - 1) //n个村庄总共搭建n-1条边
	{
		while (i <= Emax && find(E[i].x) == find(E[i].y)) i++; //找到第一个不在一个连通分支内的点
		if (i > Emax) break; //如果i>Emax,说明所有村庄已经可以通话了,退出循环
		ans += E[i].p;  //这是一条需要搭建的边,加上它
		unity(E[i].x, E[i].y);  //合并为一个连通分支
		num++;  //边的条数+1
	}
	printf("%d\n", ans);
}

前面kruskal算法的代码有点丑
下面给出相对好一点的模板
模板参考:最小生成树详解+经典例题

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
	int x,y;//树一条边的起点和终点;
	int w;// 边权(边的价值或大小)
}a[110];
int pre[110];//每个点的信息; 并查集所用;
bool cmp(struct node a,struct node b)
{
	return a.w<b.w;
}
int find(int root)// 查找函数(并查集)
{
	if(root!=pre[root]) root=find(pre[root]);
	return pre[root];
}
int main()
{
	int n,i,m;
	while(scanf("%d %d",&n,&m),n)
	{
		memset(pre,0,sizeof(pre));
		int sum=0,num=0;
		for(i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);//输入各个边的起点、终点和权值;
		sort(a+1,a+1+m,cmp);// 对各个边进行排序;
		for(i=1;i<=n;i++) pre[i]=i;
		for(i=1;i<=m;i++)// 从小到大开始枚举测试边;用并查集判断是否构成环; 不构成则加入;
		{
			int f1=find(a[i].x);
			int f2=find(a[i].y);
			if(f1!=f2)
			{
				pre[f1]=f2;
				sum+=a[i].w;//存储最小的边权和;
				num++;
			}
			if(num==n-1) break;// 满足树的性质; 边的条数==点的个数-1;
		}
		if(num==n-1)//判断是否存在;
		printf("%d\n",sum);
		else printf("?\n");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值