DS村村通工程(Kruskal算法)

题目描述

"村村通"是国家一个系统工程,其包涵有:公路、电力、生活和饮用水、电话网、有线电视网、互联网等等。

村村通公路工程,是国家为构建和谐社会,支持新农村建设的一项重大举措,是一项民心工程。又称“五年千亿元”工程

该工程是指中国力争在5年时间实现所有村庄通沥青路或水泥路,以打破农村经济发展的交通瓶颈,解决9亿农民的出行难题。

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

要求用Kruskal算法求解

输入

第1行:顶点数n

第2行:n个顶点编号

第3行:边数m

接着m行:m条边信息,格式为:顶点1 顶点2 权值

输出

第1行:输出最小生成树的权值之和

接着n-1行对应n-1条边信息

如果能找到最小生成树,按树的生长顺序输出, 边顶点按数组序号升序输出

如果输入数据不足以保证畅通,则直接输出−1,无需输出任何边信息

输入样例1

6
v1 v2 v3 v4 v5 v6 
10
v1 v2 6
v1 v3 1
v1 v4 5
v2 v3 5
v2 v5 3
v3 v4 5
v3 v5 6
v3 v6 4
v4 v6 2
v5 v6 6

输出样例1

15
v1 v3 1
v4 v6 2
v2 v5 3
v3 v6 4
v2 v3 5

NOTICE:

克鲁斯卡尔算法(Kruskal)主要有以下几点需要注意:(1)Kruskal算法是以边为操作对象,因此需定义类Edge,图的私有属性中,用edge数组代替邻接矩阵(2)所需变量有weightsum,为最小生成树的权值之和;(3)算法分两步,一是对边进行排序,二是选边;选边从小到大挑选(这也是排序的原因),并且选出来的边不构成闭环;那如何判断选的边是否会构成闭环呢:这里采用标号法,将所有顶点的flag初始化为其下标,每选择一条边,便将边两端的顶点修改为一致;因此,选边时不构成闭环的条件就是这条边两端的顶点的flag不相等;这个过程有很多细节需要注意,具体见代码注释;

#include <iostream>
#include <algorithm>
using namespace std;

class Edge
{
public:
	string start, end;
	int weight;
};

bool cmp(Edge e1, Edge e2)
{
	if (e1.weight < e2.weight)
		return true;
	return false;
}

class Graph
{
private:
	string* data;
	Edge* edge;
	int vertexnum;
	int edgenum;
	int* flag;//顶点标号,判断是否构成闭环
	int find(string s)//根据顶点信息,返回顶点下标
	{
		for (int i = 0; i < vertexnum; i++)
			if (s == data[i])
				return i;
	}
public:
	Graph()
	{
		cin >> vertexnum;
		data = new string[vertexnum];
		for (int i = 0; i < vertexnum; i++)
			cin >> data[i];

		cin >> edgenum;
		edge = new Edge[edgenum];
		for (int i = 0; i < edgenum; i++)
			cin >> edge[i].start >> edge[i].end >> edge[i].weight;

		flag = new int[vertexnum];
		for (int i = 0; i < vertexnum; i++)//全都初始化为自己的下标
			flag[i] = i;
	}
	~Graph()
	{
		delete[]data;
		delete[]edge;
		delete[]flag;
	}
	void Kruskal()
	{
		Edge* result = new Edge[vertexnum - 1];//存储结果,方便输出
		int index = 0;//result的下标
		int weight_sum = 0;


		//升序排序
		sort(edge, edge + edgenum, cmp);

		//选边,只需要n-1条边
		for (int i = 0; i < edgenum && index != vertexnum-1; i++)
		{
			if (flag[find(edge[i].start)] != flag[find(edge[i].end)])//这条边的start的flag 不等于 这条边的end的flag
			{
				result[index++] = edge[i];
				weight_sum += edge[i].weight;
			}
				

			//把这条边的两个顶点的flag改为一致(哪个赋给哪个都可以)
			//有时候是两个分量的连接,这时候改的就不止一个顶点,是其中一个分量的所有顶点,都改为与另一个分量一致
			//这里统一将end改为与start一致
			int tmp = flag[find(edge[i].end)];//一定要用一个临时变量记录,因为原来的end的flag会被改为与start一致
			for (int j = 0; j < vertexnum; j++)
			{
				if (flag[j] == tmp)
					flag[j] = flag[find(edge[i].start)];
			}
		}



		//不连通的输出
		for (int i = 0; i < vertexnum - 1; i++)
		{
			if (flag[i] != flag[i + 1])
			{
				cout << "-1" << endl;
				return;
			}
		}


		//输出
		cout << weight_sum << endl;
		for (int i = 0; i < vertexnum - 1; i++)
		{
			//题目要求边顶点按数组序号升序输出(破题要求真多)
			if (find(result[i].start) < find(result[i].end))
				cout << result[i].start << " " << result[i].end << " " << result[i].weight << endl;
			else
				cout << result[i].end << " " << result[i].start << " " << result[i].weight << endl;
		}
			
	}
};

int main()
{
	Graph g;
	g.Kruskal();
	return 0;
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值