POj 2075 Tangled in Cables(最小生成树)

POj 2075 Tangled in Cables

题意: 给出电缆长度L,有N个村庄,给出M条村庄之间的信息,信息格式:

村庄A 村庄B 距离D

,问用已有的长度为L的电缆能否把所有村庄连接起来,如果能需要多少电缆。

思路: 明显的最短生成树的模板题。再次复习一下克鲁斯克尔算法(kruskal)

kruskal算法步骤:

  1. 新建图G,G中拥有原图中相同的节点,但没有边
  2. 将原图中所有的边按权值从小到大排序
  3. 从权值最小的边开始,如果这条边连接的两个节点于图G中不在同一个连通分量中,则添加这条边到图G中
  4. 重复3,直至图G中所有的节点都在同一个连通分量中

为什么这样产生的一定是最小生成树呢?可以用反证法证明。关键在于第三步选择的边的权值是当前最小的,而且这条边一定在最小生成树中。如果这条边不在最小生成树中,它连接的两个连通分量最终还是要连起来的,通过其他的连法,那么另一种连法与这条边一定构成了环,而环中一定有一条权值大于这条边的边,用这条边将其替换掉,图仍旧保持连通,但总权值减小了

在第三步中需要使用判断两个节点是否属于同一个联通分量,需要使用并查集。并查集思路简单,核心思想是用数组表示树结构。定义了一个parent数组,令其长度和图中的顶点数一致,并且全部初始化为-1,使用该数组表示集合的树结构。比如我们设置parent[1] = 0, parent[2] = 0,表示1号顶点的父节点为0号顶点2号顶点的父节点也为0号顶点,其中-1表示该顶点为独立的顶点,还没有父节点,也就是还不属于任何一个集合,如下图所示:
在这里插入图片描述

kurskal算法假设一开始所有点都是孤立的,分别属于一个联通分量,这样N个点属于N个不同的集合。每次选取一条边将两个顶点合并时需要查找parent数组,看两个顶点是否属于同一个集合,不属于同一个集合则合并,联通分量减少一个。具体细节可以看我的并查集介绍博客

代码:

#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
using namespace std;



class Edge {
public:
	int start, end;
	double distance;
	Edge(int s, int e, double d) : start(s), end(e), distance(d) {}
};

int cmp(Edge e1, Edge e2) {
	return e1.distance < e2.distance;//从小到大排序
}



int find_root(vector<int>& parent, int x) {
	if (parent[x] == x)//自己就是根节点
		return x;
	else return parent[x] = find_root(parent, parent[x]);
}

int union_op(int x, int y, vector<int>& parent) {
	int x_root = find_root(parent, x);
	int y_root = find_root(parent, y);

	if (x_root == y_root) return 0;
	else {
		parent[x_root] = y_root;
		return 1;
	}
}


double kruscal(const vector<Edge>& edges_vec, int n, vector<int>& parent) {
	int component = n;//图中联通分量个数,初始每个顶点都是一个联通分量
	double cost = 0.0;
	for (int i = 0; i < edges_vec.size(); i++) {
		Edge e = edges_vec[i];
		if (union_op(e.start, e.end, parent)) {
			component--;
			cost += e.distance;
		}
	}
	if (component == 1)//联通分量只有一个,图联通
		return cost;
	else return -1.0;//不是联通图
}


int main() {
	double cable_length = 0.0;
	while (cin >> cable_length) {
		map<string, int> dic;
		int vertex = 0;
		cin >> vertex;
		vector<int> parent(vertex + 1, 0);
		for (int i = 0; i < parent.size(); i++) parent[i] = i;

		string tmp;
		for (int i = 0; i < vertex; i++) {
			cin >> tmp;
			dic[tmp] = i;
		}

		int edges = 0; cin >> edges;
		string start, end;
		vector<Edge> edges_vec;
		double distance = 0.0;
		for (int i = 0; i < edges; i++) {
			cin >> start >> end >> distance;
			edges_vec.push_back(Edge(dic[start], dic[end], distance));
		}

		sort(edges_vec.begin(), edges_vec.end(), cmp);

		double ans = kruscal(edges_vec, vertex, parent);
		if (ans > cable_length)
			printf("Not enough cable\n");
		else printf("Need %.1lf miles of cable\n", ans);

	}

	return 0;
}

在这里插入图片描述

参考:维基百科-克鲁斯卡尔算法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值