并查集-越多越好

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

我们先看一个简单的例子:

例子:越多越好

问题描述

某公司要求它的一些员工从事若干个项目。因这些项目相当复杂,项目是分期进行的,但每个项目需要熟悉该项目的人数越多越好。当然对人员有一定的要求。

公司的员工很多,达10000人,编号从1到10000。员工们中如果有人熟悉这个项目,那么与其认识的人只要讨教一些技巧后也能知道项目如何进行,经过传帮带后所有与其直接或间接认识的人也掌握了从事项目的技巧。为便于交流与项目修改,要求从事项目的人都直接或间接认识。

现在给定直接认识的人员对,您应该做出正确的选择。

输入
有若干个项目。每个项目的数据中第一行是一个整数n (0 ≤ n ≤ 100000) ,即该项目中直接认识的人数对。接下来有n行,每行是一个用空格隔开的数对A和B,表示他们认识,(A ≠ B, 1 ≤ A, B ≤ 10000000) 。
输出
对每个项目输出从事项目的最大人数。

输入样例
4
1 2
3 4
5 6
1 6
4
1 2
3 4
5 6
7 8
输出样例
4
2

首先进行初始化,将每个元素看作一个树,他们的父节点指向自己。

然后将想认识的两个人看作一个集合,修改元素的根节点:{1,2},{3,4},{5,6},{1,6}。

将每个集合构造一个树:
unionfind.jpg

当执行到{1,6} 是,查询 1 的根节点是 2,这时就将 6 根节点(即自己)和 1 的根节点连接,从中选择一个做根节点。
unionfind2.jpg

这是就可以看出最多的一组就是 2 为根节点的那一组,即输出 4。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* @author corlymeng.com
* @date 2015年9月12日
*/
package union.find;
import java.util.Scanner;

/**
* 并查集
*/
public class Exec1 {
// 存放每个元素的父节点,默认指向自己。
int[] father;
// 每个元素的子孙节点个数,默认为 0
int[] son;
// 员工总数
final int num = 1000;
private void init(){
father = new int[num+1];
son = new int[num+1];
for (int i = 0; i < father.length; i++) {
father[i] = i;
son[i] = 0;
}
}
/**
* 找出 s 节点的父亲节点。
* 这个递归的查找会将所有的 s 的孙子以下节点的指向都修正为 s,对并查集进行优化。
* @param s
* @return
*/
private int findRoot(int s) {
if (s != father[s]) {
// 修正节点的指向
father[s] = findRoot(father[s]);
}
return father[s];
}
/**
* 将 x 和 y 所在的集合合并。
* 默认将节点少的树合并到节点多的树上。
* @param x
* @param y
*/
private void union(int x, int y) {
x = findRoot(x);
y = findRoot(y);
if (x == y) {
return;
}
//修改父节点和子节点
if (son[x] > son[y]) {
father[y] = x;
son[x] = son[x] + son[y] + 1;
} else {
father[x] = y;
son[y] = son[y] + son[x] +1;
}
}
public void start(){
Scanner in = new Scanner(System.in);
int n = in.nextInt();
init();
for (int i = 1; i < n+1; i++) {
int x = in.nextInt();
int y = in.nextInt();
union(x, y);
}
}
public int max() {
int max = 1;
for(int i=2;i<=num;i++)
if( son[max] < son[i])
max = i;
return son[max]+1;
}
public static void main(String[] args) {
Exec1 exec1 = new Exec1();
exec1.start();
System.out.println(exec1.max());
}
}

并查集的精髓(即三个操作)

  1. init 把每一个元素初始化为一个集合,初始化后每一个元素的父亲节点是它本身。
  2. findRoot 查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!
    判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
  3. union(x,y) 合并x,y所在的两个集合。合并两个不相交集合操作很简单:
    利用 findRoot 找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。

欢迎关注我的其它发布渠道