并查集是一种数据结构,是树的一种应用,用于处理一些不交集(一系列没有重复元素的集合)的合并以及查询问题。并查集支持如下操作:
- 查询:查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。这个操作一般是为了判断两个元素是否在同一个集合之中。
- 合并:将两个集合合并为一个。
- 添加:添加一个新集合,其中有一个新元素。不如查询和合并操作重要。
我们来看下面一个比较有趣的并查集应用的例子:
话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?
我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。如此一来,门派便产生了。
在上面的例子中,我们可以认为每个门派都是一个集合,门派的掌门人作为集合的“代表元素”,通过确认双方的掌门人是否是同一个人来确定对方是否是自己人。对于没有门派的大侠,我们也可以认为他自己组成一个门派,掌门人就是他自己。集合的合并就类似于门派的合并,门派合并后需要重新推出一个掌门人,也就是要重新选取集合的“代表元素”。
实现方法一
实现方法一总是记住一个集合中编号最小的元素,类似一个门派中的大侠总是记住自己的掌门人是谁。
- 我们定义一个数组set[1…n],其中set[i]表示元素i所在集合;
- 用编号最小的元素标记所在集合。
我们来看下面一个例子,假设有以下不相交集合:
{1, 3, 7}, {4}, {2, 5, 9 10}, {6, 8}
数组set[1…n]为:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
set[i] | 1 | 2 | 1 | 4 | 2 | 6 | 1 | 6 | 2 | 2 |
其中1, 3, 7组成一个集合,他们中间最小为1,因此set[1]、set[3]、set[7]都为1。
假设我们现在要将{1, 3, 7}、{2, 5, 9 10}这两个集合合并,合并方式如下:
数组set[1…n]更新为:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
set[i] | 1 | 1 | 1 | 4 | 1 | 6 | 1 | 6 | 1 | 1 |
代码实现
初始化
初始时我们传入元素个数,并将set[i]设置为自身。
private int[] set;
/**
* 构造方法,初始化set数组
*
* @param num 元素数量
*/
public MergeFindSet1(int num) {
set = new int[num];
for (int i = 0; i < num; i++) {
// 初始时初始化为自身
set[i] = i;
}
}
查找方法
查找方法比较简单,由于set中记录的永远是集合当中的代表元素,因此只需要返回set[i]即可:
/**
* 查询某个元素属于哪个集合
*
* @param num
* @return
*/
public int find(int num) {
return set[num];
}
合并方法
在并查集的合并方法中,我们传入两个元素的编号,代表这两个元素在同一个集合当中。我们先找到两个元素所在集合的代表元素,接着找两个代表元素中编号大的那个,将编号大的元素的set[max]赋值为编号小代表元素min。同时由于方法一总是记住的是集合当中编号最小的,因此还需要更新set[i]=max的所有元素。
/**
* 集合的合并操作
*
* @param a
* @param b
*/
public void merge(int a, int b) {
int findA = find(a);
int findB = find(b);
int max = Math.max(findA, findB);
int min = Math.min(findA, findB);
// 用编号小的元素标记所在集合
// 同时还需要把以前同一个集合中的元素的set[i]更新
for (int i = 0; i < set.length; i++) {
if (set[i] == max) {
set[i] = min;
}
}
}
测试
public static void main(String[] args){
MergeFindSet1 mergeFindSet1 = new MergeFindSet1(11);
mergeFindSet1.merge(1, 3);
mergeFindSet1.merge(3, 7);
mergeFindSet1.merge(2, 5);
mergeFindSet1.merge(5, 9);
mergeFindSet1.merge(9, 10);
mergeFindSet1.merge(6, 8);
for (int i = 0; i < 11