- 连通网络的操作次数
用以太网线缆将 n 台计算机连接成一个网络,计算机的编号从 0 到 n-1。线缆用 connections 表示,其中 connections[i] = [a, b] 连接了计算机 a 和 b。
网络中的任何一台计算机都可以通过网络直接或者间接访问同一个网络中其他任意一台计算机。
给你这个计算机网络的初始布线 connections,你可以拔开任意两台直连计算机之间的线缆,并用它连接一对未直连的计算机。请你计算并返回使所有计算机都连通所需的最少操作次数。如果不可能,则返回 -1 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-operations-to-make-network-connected
处理元素分组,管理不相交集合问题——显然这道题用并查集。
首先我们来讲什么是并查集:
什么是并查集?
这是一个优雅而简洁的数据结构之一,它的重要思想或者说特点在于:用集合中的一个元素来表示它所在的集合。通俗一点,我们可以把它看成,给每个集合都指定一个头目,或者说根节点。
怎么理解呢?我们就直接从这道题来理解并查集。
步骤:
初始化
我们以示例 1 为例:
以并查集的思想来看,四台电脑,初始化出来就是四个元素,他们刚开始是四个集合,每个集合的根节点是自己:
int []parent = new int[n];
for(int i = 0; i < n; i ++)
parent[i] = i;
然后我们给出他们的连接条件,connections = [[0,1],[0,2],[1,2]]。根据连接条件,我们就要开始进行集合合并,这里我们要介绍关于并查集的两个操作:查找(find)和合并(union)
查找(find)
并查集是使用集合中的元素(根节点)来表示这个集合,所以我们想要知道某个元素所在的集合(根节点),我们需要查询操作:(这里用递归查询父节点的写法来实现查询)
// 查找父节点
private int find(int node){
if(parent[node] == node)
return node;
else
// 路径压缩
return (parent[node] = find(parent[node]));
}
当集合只有一个元素时,他自己就是集合根节点。
当集合有多个元素时,会查询自己的父节点的父节点进行递归查询。
每次查询时都会出现重复查询的时候(如左图)所以我们在每次查询的时候进行一次更新,进行路径压缩(如右图)
合并(union)
当可以知道每个元素所在的集合(实现了查找)之后,就可以根据示例给的连接条件进行元素集合的合并:
// 集合合并
private void union(int node1,int node2){
int parent1 = find(node1);
int parent2 = find(node2);
if(parent1 == parent2)
return;
parent[parent1] = parent2;
}
如果两个元素的通过 find 找到的根节点相同,则不进行操作
如果根节点不同,则表示集合不同,则将其中一集合的根节点作为另一集合根节点的父节点
这里有个前后问题,我们这里没有去考虑,但是如果要考虑初始化后的结构问题,可以按秩合并。这里就不具体讲按秩合并。
比如 2 连 3
所以所有代码:
class Solution {
// 并查集
private int[] parent;
public int makeConnected(int n, int[][] connections) {
// 至少需要 n-1 条,否则不成立
if(n-1 > connections.length)
return -1;
// 初始化
parent = new int[n];
for(int i = 0; i < n; i ++)
parent[i] = i;
// union 比较合并集合
// each-for
for(int[] line : connections){
union(line[0],line[1]);
}
int count = 0;
for(int i = 0; i < n; i ++){
// 有多少个根节点,就有多少个集合
if(parent[i] == i)
count++;
}
// 集合数 -1 等于连线数。
return count - 1;
}
// 查找父节点
private int find(int node){
if(parent[node] == node)
return node;
else
// 路径压缩
return (parent[node] = find(parent[node]));
}
// 集合合并
private void union(int node1,int node2){
int parent1 = find(node1);
int parent2 = find(node2);
if(parent1 == parent2)
return;
parent[parent1] = parent2;
}
}
解题思路:首先进行并查集的初始化,根据连接对元素进行合并操作,然后对每个点进行查询,查询有多少个根节点(也就是find找到的还是自己)也就是集合的个数,有多少个集合,就需要 n-1 根线将他们连在一起。最后在前面加上特殊判断,n 个点想连在一起,至少需要 n-1 根线,所以直接排除掉无法连通的情况。