并查集,就如同它的名字一般。对集合进行查询,合并。
我们用一道题学习并查集。
hdu1232 畅通工程
有n个城市,城市之间一共有m条道路,给出两两城市有路,求再建设几条路就可以完成所有道路的互通。
我们将互通的城市建立一个树,例如第一组测试数据
4 2
1 3
4 3
现在我们把互通的城市连一条线。就形成了一颗树。现在一共两颗树,我们将两颗树连一条线。就形成了所有的道路变成了互通。输出就为1.
那我们怎么将这些节点变成一个树呢?
我们要借助一个数组模拟一下树。
static int set[]; //初始化 set[i] = i;
当i与j是一颗树时,将set[i] = j; or set[j] = i;
set数组就形成了一颗颗树。例如:set[3] = 2; set[2] = 1;set[1] = 1;
{1,2,3}在set集合中为一颗树。
我们怎么去操作查询两个节点同一集合呢。
public static int find(int k) {
int x = k;
while(x != set[x]) {
x = set[x];
}
return x;
}
我们先将一个节点一直查询它的父节点。将它变得它的父节点。在接着刚刚的操作。直到它的父节点与本身一致。在操作另一个节点。如果两个节点的根节点一致说明两个节点为一个树,在一个集合中。但如果两个根节点不一致的情况,说明两个节点不在同一集合内。
例如刚刚的 :set[3] = 2; set[2] = 1;set[1] = 1;
我们查询1,2是否在一个集合中。
我们先查询节点1.x = 1 进行循环查找set ,
x = set[x];
x = set[x(x = 1)]; (x这时变成了2)
x = set[x (x = 2)],(x这时变成了3)
节点1的根节点为3
我们再去查找节点2, 同上方法一致。 x = set[x (x = 2)],(x这时变成了3)
节点2的根节点为3
说明1,2为同一集合。
两个集合怎么合并呢?
public static void merge(int k, int l) {
int x = find(k);
int y = find(l);
if (x != y) {
set[x] = y;
}
}
我们找到两个节点的根节点。将两个根节点合并就完成了两个集合合并过程。借助查找集合根节点。将查到的一个根节点的父节点变成另一个根节点就可以了。
set[x] = y;
这步操作完成两个集合合并。
并查集的学习就完成了。
这道题运用并查集可以写成:
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main{
static int set[];
@SuppressWarnings("resource")
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
int n = scanner.nextInt();
if (n == 0) {
break;
}
int m = scanner.nextInt();
set = new int[n + 1];
for (int i = 0; i <= n; i++) {
set[i] = i;
}
for (int i = 0; i < m; i++) {
int x = scanner.nextInt();
int y = scanner.nextInt();
bit(x, y);
}
int sum = 0;
for (int i = 1; i < set.length; i++) {
if (set[i] == i) {
sum++;
}
}
System.out.println(sum - 1);
}
}
public static void bit(int k, int l) {
int x = find(k);
int y = find(l);
if (x != y) {
set[x] = y;
}
}
public static int find(int k) {
int x = k;
while(x != set[x]) {
x = set[x];
}
return x;
}
}
这种方式的并查集可以优化。
如同左图我们查询节点3,4是否为一个集合时,我们从4查到3,查到2,查到1。浪费了时间。
我们就可以将左图变成右图。这样查询节点4时,直接就查到了节点1.
我们怎么操作呢?
public static int find(int k) {
if (k == set[k]) {
return k;
}
return set[k] = find(set[k]);
}
我们只需要在查询过程中,边查询边更新节点的父节点,让其变成根节点就可以了。
这样我们就完成了路径压缩的过程。
简单的自我理解,将就看。