并查集算法是简单却又非常实用的一种算法。书上的例子可以非常清楚到位的概括这个算法使用的场景,那就是平面上有n个点,给定一条边既可以将两个点联通起来,现在有大量的这样的边,问最后有多少个连通子图?
以下是并查集算法的功能;
- 从一堆连接任意两个顶点的边信息中创建并查集
- 可以查询到任意两个顶点是否连通
- 对于指定边能够将该边的两个顶点连通起来让他们属于同一个子图
- 能够找到某一个顶点所在子图的根在哪儿
- 计算一个图中的连通分量有多少个
算法的api
public class UF
UF(int n); // 创建包含n个顶点的并查集
void union(int p, int q); // 连接顶点p和q
int find(int p); // 查找p所在分量的标识符
boolean connect(int p, int q); // 判别两个顶点是否连通
int count(); //返回图中总共的连通分量数目
书中有三种实现方式,Quick-Find,Quick-Union,Union-Find
简单介绍Quick-Find和Quick-Union,重点为Union-Find
Quick-Find:
所有的节点都赋予一个 ID,如果两个节点相连,则将这两个节点的 ID 设成一样的,这样,这两个节点便属于同一个组了。网络中每个组都有了一个唯一的 ID。只要节点 p 和 q 的 ID 相同,则认为节点 p 和 q 相连。我们用数组来放置节点 ID,find()方法可以快速返回 ID,所以我们的第一个算法就叫做 QuickFind。
Quick-Find 算法分析:
QuickFind 算法在最坏情况下,几乎需要遍历整个数组,如果数组很大 、需要连接的节点对很多的时候,QuickFind算法的复杂度就相当大了。此时时间复杂度为O(N^2)
Quick-Union:
使用树结构来避免遍历整个数组的操作。树的所有节点都有一个共同的根节点,每个树只有一个根节点,那每个树就可以代表一个组。find()方法返回的是根节点,connect()方法判断是否联通时,只需判断两个数的根节点是否相同。union(p,q)的时候,只要把p所在的树附加到q所在的树的根节点,这样,p和q就在同一树中了。
Quick-Union算法分析:
Quick-Union方法比起Quick-Find,其不用遍历整个数组,效率提高很多。但是在最坏的情况下(最后变为一整列的形况,即一只有一棵,没有分支),此时复杂度变成2(1+...+N)=(N+1)N,接近N的平方了。
Union-Find:
Union-Find算法在Quick-Union的基础上算上了树大小的权重,在union(p,q)的方法中加入了对两个树大小的判断,让小的树的根链接到大的树的根,来减少合并后树的深度(相比Quick-Union)。又称为优化路径压缩。
代码实现:
public class WeightedQuickUnionUF {
private int[] id;//父链接数组
private int[] sz;//各个根节点对应的分量的大小
private int count;//连通分量的数量
public WeightedQuickUnionUF(int N) {
count = N;
id = new int[N];
sz = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;//触点的索引和值相等
sz[i] = 1;//每个分量都为1,合并后加到大的树上
}
}
public int count() {
return count;
}
public boolean connected(int p, int q) {
return find(q) == find(p);
}
public int find(int p) {
//跟随链接找到根节点
while (p != id[p]) p = id[p];
return p;
}
public void union(int p, int q) {
int i = find(p);
int j = find(q);
if (i == j) return;
//将小树的节点链接到大叔的根节点
if (sz[i] < sz[i]) {
id[i]=j;
sz[j]+=sz[i];
}else {
id[j]=i;
sz[i]+=sz[j];
}
count--;
}
public static void main(String[] args) {
WeightedQuickUnionUF test = new WeightedQuickUnionUF(100);//大小为n的父链接数组
int[] testnumber={1,2,3,3,5,6,56,32,23,56};
for (int i=0;i<testnumber.length;i=i+2){
System.out.println("this time is"+testnumber[i]+" "+testnumber[i+1]);
if (test.connected(testnumber[i],testnumber[i+1])){
System.out.println("他们已经链接了");
continue;
}
test.union(testnumber[i],testnumber[i+1]);
System.out.println("新生成链接");
}
System.out.println("分量有" + test.count + "个");
}
}
最后输出
原来有100个分量,组成了4个链接,所以最后份量为96,显示正确
参考链接:
https://blog.youkuaiyun.com/rebornyp/article/details/79053804
https://www.cnblogs.com/learnbydoing/p/6896472.html?utm_source=itdadao&utm_medium=referral