连接所有点的最小费用
- 来源 : LeetCode - 连接网络的操作次数
- 难度 : 中等
- 标签 : dfs, 并查集
题目描述
-
用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
-
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
-
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1
解法一:DFS
解法构思:
- 这个问题可以转化成此计算机网络构成的图中有几个连通分量,利用冗余的边将所有的连通分量连起来就可以构成连通图。
- 连通图的生成树构成的子图就是要求边最少的能连通的子图,所以只要保证此题中的线缆的个数不少于计算机数目 - 1即可。
- 这样若符合构成最少边连通图的条件此题的结果即连通分量数目减一(将M个连通子图相连需要M-1条边)。
- 若本身边的数目就不够将图重构成生成树,即是此题的返回-1的情况。
上手编程:
class Solution {
public:
vector<vector<int>> edges; // 邻接表
vector<int> visited; // 记录是否被遍历
void dfs(int start) { // DFS
visited[start] = true;
for (auto & e : edges[start]) {
if (!visited[e])
dfs(e);
}
}
int makeConnected(int n, vector<vector<int>>& connections) {
if (connections.size() < n -1) return -1; // 边数太少直接返回-1
int res = 0;
visited.resize(n);
edges.resize(n);
for (auto & e : connections) { // 构建邻接表
edges[e[0]].push_back(e[1]);
edges[e[1]].push_back(e[0]);
}
for (int i = 0; i < n; ++i) { // 寻找连通分量,每找到一个计数加一
if (!visited[i]) {
dfs(i);
++res;
}
}
return res - 1; // 连通分量个数 - 1
}
};
解法二:并查集
解法构思:
- 并查集能够快速判断两个元素是否在同一集合,能够快速合并两个集合。
- 并查集本身就是一个树(边最少连通图),我们可以利用并查集模拟网络最终建构的过程,我们保证将每个在同一连通分量的点都执行合并,最终得到的并查集对应的连通分量的组成肯定和网络对应的相同。
- 过程:一开始使每一个顶点成为一个连通分量,之后利用并查集合并网络中有边相连的点。每成功合并一次,连通分量个数减一,最后就得到了网络的连通分量个数。
上手编程:
// 并查集
class UnionFindSet {
private:
vector<int> m_parents;
vector<int > m_ranks;
public:
UnionFindSet(int n) : m_parents(n), m_ranks(n, 1) {
for (int i = 0; i < n; ++i) {
m_parents[i] = i;
}
}
bool unite(int a, int b) {
int x = find(a);
int y = find(b);
if (x == y) return false;
if (m_ranks[x] < m_ranks[y]) {
swap(x, y);
}
m_parents[y] = x;
m_ranks[x] += m_ranks[y];
return true;
}
int find(int a) {
return m_parents[a] == a ? a : (m_parents[a] = find(m_parents[a]));
}
bool same(int a, int b) {
return find(a) == find(b);
}
};
class Solution {
public:
int makeConnected(int n, vector<vector<int>>& connections) {
if (connections.size() < n - 1) return -1;
UnionFindSet s(n);
int res = n; // 一开始连通分量个数为n
for (auto & e : connections) {
if (s.unite(e[0], e[1])) --res; // 每成功合并就更新连通分量个数
}
return res - 1;
}
};