并查集 :是一种树形的数据结构,用于处理一些不相交的集合的合并和查询问题。通常在使用中以森林来表示。
作用:可以判断网络(不单是计算机网络)中节点的连接状态。
对一组数据主要支持以下几个动作:
union(p,q)//将p,q所在的集合合并为同一集合
isConnected(p,q)//查看p,q是否是同一集合的元素
find( p )//查找元素所在的集合,即根节点
代码实现:
``
//并查集接口
public interface UnionFind {
int getSize();
boolean isConnected(int p, int q);
void unionElements(int p, int q);
}
``
/**
* @author shy_black
* @date 2019/4/22 15:57
* @Description:
* 下标 0 1 2 3 4 5 6 7 8 9
* id: 0 1 2 3 4 5 6 7 8 9 元素
* 0 1 0 1 0 1 0 1 0 1 id集合
* quick find
*/
public class UnionFind_1 implements UnionFind {
private int[] id;
public UnionFind_1(int size) {
id = new int[size];
for(int i = 0;i < id.length; i++) {
//每一个元素都属于自己的集合编号
id[i] = i;
}
}
/**
*
* @param p:查找元素p所对应的集合编号
* @return
*
*/
private int find(int p) {
if(p < 0 && p >= id.length)
throw new IllegalArgumentException("p is out of bound...");
return id[p];
}
@Override
public int getSize() {
return id.length;
}
/**
* @param p
* @param q
* @return
* 查看元素p和元素q是否所属一个集合
*/
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
//合并元素p和元素q所属的集合
//将pID修改为qID
@Override
public void unionElements(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;
}
}
}
优化:当前的unionElement(p,q);方法在每次的合并集合时,会将所有的p集合中的元素修改为q集合的元素。如果p集合很大,但q集合很小时,这样做很浪费时间。
``
//以下的方法增加了一个记录集合大小的数组 sz,
//将数组换成了树结构,在find时可以直接找到集合的根节点。
//在unionElement时,直接根据节点的大小将根节点合并,
//而不是遍历数组进行更改,效率提高
//
public class UnionFind_2 implements UnionFind {
private int[] parent;
private int[] sz;
public UnionFind_2(int size) {
parent = new int[size];
sz = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
sz[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
//查找过程,查找元素p对应的集合编号
//O(h)复杂度,h为树的高度
private int find(int p) {
if (p < 0 && p >= parent.length) {
throw new IllegalArgumentException("非法下标");
}
while (p != parent[p]) {
//如果p != parent[p],让p等于parent[p],不断寻找p的根
p = parent[p];
}
return p;
}
//查看p和q是否所属一个集合
//O(h)复杂度,h为树的高度
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot)
return;
//如果p的节点数小于q的节点数,
// 让p的根节点指向q的根节点
if (sz[pRoot] < sz[qRoot]) {
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
}else {
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
优化:还可以将记录一个rank[]数组,记录集合的层数,根据层数将层数小的根节点合并到层数多的根节点上。如果层数相同,合并节点后rank[Root]加一即可。
``
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if(rank[pRoot] < rank[qRoot])
parent[pRoot] = qRoot;
else if(rank[pRoot] > rank[qRoot])
parent[qRoot] = pRoot;
else {
parent[pRoot] = qRoot;
rank[qRoot] += 1;
}
}
最后,可以通过在find方法中修改方法体内容达到路径压缩的作用
``
//在当前节点不是根节点时,通过将当前节点连接到当前节点的父亲节点的父亲节点上,达到路径压缩的目的,从而减少并查集的深度,增加查找效率
private int find(int p){
if(p < 0 || p >= parent.length)
throw new IllegalArgumentException("p is out of bound.");
while( p != parent[p] ){
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}