【PTA】7-10 公路村村通 (最小生成树)(Prim+Kruskal)

7-10 公路村村通 (30 分)
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
输出样例:
12

思路分析:

该题目是给出了N个村落之间的M条路,求每个村落都连通所需要的最低成本。其实就是求保证图连通前提下的最短边,最小生成树的模板题。下面介绍求最小生成树的两种算法。

Prim算法

Prim算法思路和Dijkstra算法相似,都是贪心。只是Prim的dis数组定义为结点到树的最短距离。比如dis[j] = 1 表示j结点与树的最短距离为1.下面直接上代码。

#include 
#include 
#define MAX 1010
using namespace std;

int dis[MAX];
int vis[MAX];
int map[MAX][MAX];
int N, M;

//思路与Dijkstra算法很像,只是Prim的dis[j]表示j到这颗树的最小距离 
void Prim()
{
	memset(dis,0x3f, sizeof(dis));	//快速初始化dis为无穷大 
	dis[1] = 0;		//以1结点为初始树,自然dis[1]=0
	while(1)
	{
		int tmp = 0x3f3f3f3f;
		int k = 0;
		for(int i = 1;i <= N;i++)	//从未遍历过的顶点里找dis最小的顶点 
		{
			if(!vis[i] && dis[i] < tmp)		
			{
				tmp = dis[i];
				k = i;				//k记录离树最近的顶点(最小dis的下标)
			}
		}
		if(!k)	return;	//顶点都遍历完了,退出循环
		vis[k] = 1;		//收录该顶点 
		for(int j = 1;j <= N;j++)	//遍历该顶点的出度,更新dis 
		{
			if(!vis[j] && map[k][j] < dis[j])	//!vis[j]表示出度顶点必须是没遍历过的
			{									//map[k][j] < dis[j]表示该树到j的距离<当前dis[j]
					dis[j] = map[k][j];			//更新dis
			}
		}
	}
}

int main()
{
	int x, y, z;
	int sum = 0, flag = 1;
	memset(map, 0x3f, sizeof(map));
	cin>>N>>M;
	while(M--)	//邻接矩阵建图
	{
		cin>>x>>y>>z;
		map[x][y] = z;
		map[y][x] = z;
	}
	Prim();
	for(int i = 1;i <= N;i++) 
	{
		sum += dis[i];
		if(!vis[i]) flag = 0;	//若有顶点未遍历到,表示图不连通
	}
	if(!flag) cout<<-1<<endl;
	else cout<<sum<<endl; 
	return 0;
} 

Kruskal算法

Kruskal算法其实就是并查集+贪心,以边作为结点,若该边权值小且不构成回路(查)就把边连的两个顶点并成一个树(并),下面上代码。

#include 
#include 
#define MAX 1010
using namespace std;

int pre[MAX];
int h[MAX];
int N, M;

struct node
{
	int v;		//顶点1 
	int w;		//顶点2 
	int dis;	//权值 
}edge[3*MAX];

bool cmp(const node &a, const node &b)		//改造sort排序,以bool为返回值 
{
	return a.dis<b.dis;		//返回结构体成员的比较表达式 
}

int Find(int x)
{
	if(pre[x] == x) return x;
	return pre[x] = Find(pre[x]);
}

void Union(int x, int y)
{
	int rx = Find(x);
	int ry = Find(y);
	if(rx == ry) return;
	if(h[rx] > h[ry]) pre[ry] = rx;
	else if(h[rx] < h[ry]) pre[rx] = ry;
	else pre[ry] = rx, h[rx]++;
}

int main()
{
	int tot = 0, k = 0;
	cin>>N>>M;
	for(int i = 0;i <= N;i++)	//并查集初始化 
	{
		pre[i] = i;
		h[i] = 1;
	}
	for(int i = 1;i <= M;i++)
	{
		cin>>edge[i].v>>edge[i].w>>edge[i].dis;
	}
	sort(edge+1, edge+1+M, cmp);	//将边的权值从小到大排 
	for(int i = 1;i <= M;i++)		//以权值小到大遍历边 
	{
		if(k == N-1) break;		//已经满足N-1条边,退出循环 
		if(Find(edge[i].v) != Find(edge[i].w))	//该边不构成回路(该边两个顶点不在同一个集合树)
		{
			Union(edge[i].v, edge[i].w);		//并起来 
			tot += edge[i].dis;					//统计权值 
			k++;	//记录边个数 
		} 
	}
	if(k < N-1) cout<<-1<<endl;		//图不连通 
	else cout<<tot<<endl;
	return 0; 
}

你可能感兴趣的:(数据结构,算法)