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;
}

在這裏插入圖片描述

參考:維基百科-克魯斯卡爾算法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章