写在前面
这是今天下午做leetcode contest的第三题,一开始看到的时候以为考察的是图的连通性的问题,没能想出解决方法,后来看Discussion,发现大家都用到了Union-Find,正好《算法》也看到Union-Find,于是就去把这章看完了再来做,下面会先介绍Union-Find,然后写解题思路。
Union-Find(并查集)
由Dynamic Connectivity Problem引出。
问题Context:
有N个node(0, ……, N - 1),(p, q) pair指node p和node q连通(connection),这个连通关系是一个等价关系(自反性、对称性、传递性)。互相之间有通路的节点可以认为是在同一个component中(个人觉得这里也可以说在一个集合中)。
算法可以做到:
- 连通(connect / union)两个节点
两个节点原本处在不同的component中,若将两个节点连通,则两个component也合并为一个(做并操作) - 找到node p所在的component(一般是用该component中某个node的id来指代这个component)
- 判断两个node是否连通
- 获得component的个数
API
public void union(int p, int q);
public int find(int p);
public boolean connected(int p, int q);
public int count();
使用的数据结构
最关键的是记录每一个节点情况的数组int[] id = new int[N];
两种实现方式
这里只简单记录,《算法》书中有详细分析
Fast-Find
id数组记录的直接是每个node所在的component的identifier,union时,将两个node的identifier设为一致
public int find(int p) {
return id[p];
}
public void union(int p, int q) {
int pID = find(p);
int qID = find(q);
if (pID == qID)
return;
for (int i = 0; i < id.length; i++){
if (id[i] = pID) {
id[i] = qID;
}
}
count--; //the number of components, which is initialized as N
}
Fast-Union
这是我在解题时使用的实现
id数组传达的信息更复杂一些,node p对应的id[p]
记录的是node p所在的component中的下一个node是什么,结束node记录的id[node] == node
(类似于形成一个链表,每个node只有一个域,指向下一个node)
public int find(int p) {
while (p != id[p])
p = id[p]; //refer to the next node
//get the root
return p;
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
//decide whether the two nodes are in the same component
if (pRoot == qRoot)
return;
id[p] = q; //let p point to q
count--;
}
在上述实现中,总是将node q加入到p的component中,对此有更好的实现方法,即将权重小的树(即节点更少的component)加入到权重大的树中,但这需要额外的数据结构来保存weight,具体可以参考书本
Union-Find衍生实际问题:
- 网络中设备的连通
- (不同名的)变量等价
- 数学集合(个人觉得这是并查集的本质)
\以下是leetcode题目
原题链接
题目描述
There are n computers numbered from 0 to n-1 connected by ethernet cables connections forming a network where connections[i] = [a, b] represents a connection between computers a and b. Any computer can reach any other computer directly or indirectly through the network.
Given an initial computer network connections. You can extract certain cables between two directly connected computers, and place them between any pair of disconnected computers to make them directly connected. Return the minimum number of times you need to do this in order to make all the computers connected. If it’s not possible, return -1.
给定电脑数量和连通情况,判断要移动几根连通线才能使所有电脑连通
解题思路
根据连通情况将computer连通,并得到最后形成的component数量,要增加的连通线数量就是将原本不连通的component连通起来,因此最少为(count - 1)
,注意存在多余的连通线不够用的情况
class Solution {
public int makeConnected(int n, int[][] connections) {
//union-find
int[] site = new int[n];
int component = n, redundance = 0;
for (int i = 0; i < n; i++) {
site[i] = i;
}
for (int i = 0; i < connections.length; i++) {
int tmp = connect(connections[i][0], connections[i][1], site);
if (tmp == 1)
component--;
else
redundance++;
}
//not enough
int res = component - 1;
if (res > redundance)
return -1;
return res;
}
private int find(int p, int[] site) {
while (p != site[p]) {
p = site[p];
}
return p;
}
private int connect(int p, int q, int[] site) {
int pRoot = find(p, site);
int qRoot = find(q, site);
if (pRoot != qRoot) {
site[pRoot] = qRoot;
return 1;
}
return -1;
}
}
来源:CSDN
作者:JellyFishDing
链接:https://blog.csdn.net/JellyFishDing/article/details/104009936