定义
并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个用于此数据结构的操作:
- Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
- Union:将两个子集合并成同一个集合。
首先并查集本身是一个结构,我们在构造它的时候需要将所有要操作的数据扔进去,初始时每个数据自成一个结点,且每个结点都有一个父指针(初始时指向自己)。
因此前提就是所有集合中的数据必须是一次性提前给定的,不能以流的方式动态的加入数据到集合中。
(一)功能作用
- 查询两个元素是否属于同一个集合:isSameSet(A,B),本质上是判断元素 A 和元素 B 所属的集合是否为同一个集合。
- 两个元素各自所在的所有集合进行合并:union(A,B);
(二)前提
所有集合中的数据必须是一次性提前给定的,不能以流的方式动态的加入数据到集合中。
(三)实现步骤
-
首先给定数据集合,这里以 int 类型为例 {1,2,3,4,5}
-
初始时并查集中的每个结点都算是一个子集,我们可以对任意两个元素进行合并操作。值得注意的是,
union(nodeA,nodeB)
并不是将结点nodeA
和nodeB
合并成一个集合,而是将nodeA
所在的集合和nodeB
所在的集合合并成一个新的子集:
-
元素所属集合:
集合(一般为多叉树结构)中元素一直向上查找,一直找到一个结点的上一个结点指向自己,则该结点称为代表节点(并查集初始化时,每个结点都是各自集合的代表结点),同时该节点代表整个集合。 -
find
操作:查找两个结点是否所属同一个集合。我们只需判断两个结点所在集合的代表结点是否是同一个就可以了: -
集合合并:合并两个集合就是将结点个数较少的那个集合的代表结点的父指针指向另一个集合的代表结点。示例:3,5 集合合并
-
查找优化:查询到代表结点之后,将查询过程中经历的所有结点进行打平。
例如下面结构中查询节点 3 的代表节点,过程就是依次向上查询,一直遍历查询到 1,然后返回 1 并将沿途经过的结点2 打平(直接挂在代表结点下面)。
实现
package MyExc;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Data{
}
//用Map存父节点,这个思路也可以用在链表上,查询速度快很多
public class UnionFindSet {
//这个结构,表示key的父节点是value
public Map<Data,Data> fatherMap = new HashMap<>();
//sizeMap指的是,如果这个Data节点是代表节点,表示这个Data节点所在集合的大小;如果不是,则这条数据无效
//潜台词就是不会更新错误信息为对的
public Map<Data,Integer> sizeMap = new HashMap<>();
public UnionFindSet(List<Data> nodes) {
makeSets(nodes);
}
//使用前的初始化
public void makeSets(List<Data> nodes){
fatherMap.clear();
sizeMap.clear();
for(Data node:nodes){
//父节点设置为自己
fatherMap.put(node,node);
//大小设置为1
sizeMap.put(node,1);
}
}
//每次执行findHead就把一条长串打散了
private Data findHead(Data node){
Data father = fatherMap.get(node);
if(father!=node){
father = findHead(father);
}
//递归完后,所有的节点的父节点都是最上面的节点
fatherMap.put(node,father);
//把最终找到的father一直往回传,这样所有的节点的父节点都是最终的father
return father;
}
public boolean isSameSet(Data a,Data b){
return findHead(a) == findHead(b);
}
public void union(Data a,Data b){
if(a==null||b==null){
return;
}
Data aHead = findHead(a);
Data bHead = findHead(b);
//如果特征点不相等则执行下面
if(aHead!=bHead){
int aSetSize = sizeMap.get(aHead);
int bSetSize = sizeMap.get(bHead);
//哪个长就往哪个上面合并
if(aSetSize<=bSetSize){
fatherMap.put(aHead,bHead);
sizeMap.put(bHead,aSetSize+bSetSize);
}else{
fatherMap.put(bHead,aHead);
sizeMap.put(aHead,aSetSize+bSetSize);
}
}
}
}