1: 动态连通性
可以检测所给的点中 是否有环的:
概念:
- 并查集:(union-find sets)
一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskar算法求最小生成树。
- 并查集的精髓(即它的三种操作,结合实现代码模板进行理解):
1、Make_Set(x) 把每一个元素初始化为一个集合
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、Find_Set(x) 查找一个元素所在的集合
查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图
3、Union(x,y) 合并x,y所在的两个集合
合并两个不相交集合操作很简单:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。如图
- 并查集的优化
1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。
2、Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
package com.algorithm.common.tree;
import com.algorithm.lib.StdOut;
/**
* 动态连通性检查 (并查集算法)
* @author lijunqing
*/
public class UF {
/**
* i节点的父节点(集合)
*/
private int[] id; // id[i] = parent of i
/**
* i集合中子节点数量
*/
private int[] sz; // sz[i] = number of objects in subtree rooted at i
/**
* 集合总数
*/
private int count; // number of components
/**
* 初始化N个集合
*/
public UF(int N) { // n表示所有数值就是count
if(N < 0)
throw new IllegalArgumentException();
count=N;
id=new int[N];
sz=new int[N];
for(int i=0; i < N; i++) {
id[i]=i;
sz[i]=1;
}
}
/**
* 查找一个元素所在的集合(其精髓是找到这个元素所在集合的祖先) 就是找到改元素所在集合的祖先
*/
public int find(int p) {
if(p < 0 || p >= id.length)
throw new IndexOutOfBoundsException();
while(p != id[p]) //// 相等表示找到祖先
p=id[p];
return p;
}
/**
* 返回集合总数
*/
public int count() {
return count;
}
/**
* 判断pq是否在同一集合中
*/
public boolean connected(int p, int q) {
return find(p) == find(q);
}
/**
* 合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小
*/
public void union(int p, int q) {
int i=find(p);
int j=find(q);
if(i == j)
return;
// make smaller root point to larger one
if(sz[i] < sz[j]) { // 元素少的集合合并到元素多的集合中
id[i]=j; // i的父节点是j
sz[j]+=sz[i];
} else {
id[j]=i; // j的父节点是i
sz[i]+=sz[j]; // 改变父节点的值(值越大表示其子节点越多)
}
count--;
}
public static void main(String[] args) {
int N=10;
UF uf=new UF(N);
uf.union(1, 2);
uf.union(3, 4);
uf.union(6, 7);
uf.union(2, 7);
int a=uf.find(6);
System.out.println(a);
StdOut.println(uf.count() + " components");
}
}
0 1 1 3 3 5 1 6 8 9