目录
并查集简介
建立并查集(初始化)
建立关系
查找root与路径压缩算法
代码与测试
并查集简介
并查集的功能如下:
如果a和b是舍友,b和c是舍友,则我们认定a和c也是舍友(当然在离散数学中这是错误的,为了引入我们假定自己和自己也是舍友)。也就是说有n个人,给出m个元组(a,b)表示a和b是舍友,现在有许多查询:请你判断任意两个人之间是否有舍友关系。
复杂度来讲,建立和查询加起来大概是O(n)再加上一个常数,效率也比较高。
并查集还可以解决以下问题:
如何判断一个简单无向图是否为连通图(图一般会以二元组序列形式给出)
其实就是并查集解决 图中任意2个节点是否连通的问题。
并查集的数据结构其实是树状的,所有的节点连通的情况 相当于一个或者多个树,同一个树的节点相互连通,不同树不连通。
并查集空间复杂度只有O(n),代码更加简洁,且查询越多,平均时间复杂度越低。查询极多的时候可以认为查询的复杂度为O(1)。
建立并查集(初始化)
设parent[x]表示x的父亲节点是谁,初始parent[x]=x (假设root节点的父亲还是root,这根linux文件系统有点像)
/**
* parents 数组,parents[i]代表节点i的父亲节点。
* 如果parents[i]=k,代表i和k是连通的。
*/
int[] parents;
/** 并查集构造函数,n代表总共几个节点
* @param n
*/
public UnionFind(int n){
parents=new int[n];
for(int i=0;i<n;i++){
// 最开始每个节点i只和它自己连通
parents[i]=i;
}
}
建立关系
给出m个关系a,b,表示a和b属于同一组,也就是将两个人归并到一个组的操作,就是把b所在的树挂到a的树上,为了减小树的高度和方便,我们直接把a所在的树挂到b的根上,使a和b同在一棵树上。
当然,如果a和b本来就同在一棵树上,那就不需要合并这个操作了。而判断是否在同一棵树上的依据是是否有同一个根。
我们设find(x)表示x所在树的根节点,那么合并操作代码如下:
/** 连通节点a和节点b。
* 如果a和b原来不连通,把a的root放到b树的root下面,即b的root为总的根节点。
* @param a
* @param b
*/
public void connect(int a,int b){
if(find(a)==find(b)){
// 如果a和b的根是同一个,说明a与b已经相连,不用操作。
return;
}
// 把a的root放到b树的root下面
parents[find(a)]=parents[find(b)];
}
查找root与路径压缩算法
查找root就是find函数的功能,而find(x)函数的实现,则是顺着parent[x]函数一直回溯,直到parent[x]==x就找到root了,(根节点的父亲还是自己)。
回溯次数跟树的高度有关,为了优化,还需要采用优化路径压缩,减小树的高度。也就是在回溯的过程中,不断把当前节点重新挂载到上一层祖先上,只要保证他们的root不变,其他的父亲关系都不影响大局。用代码就是parent[x]=find(parent[x]);(令x的父亲等于爷爷,这就降低了树的高度而不改变共同的root祖先)
/** 返回节点i的根节点编号,并且把i到根节点的所有节点的父节点,都变成根节点,进行了路径压缩
* @param i
* @return
*/
public int find(int i){
if(parents[i]==i){
return i;
}
// 将i的父亲节点变为i这个树的根节点(由于递归操作,i到根节点的所有节点的父节点,都变成根节点)
return parents[i]=find(parents[i]);
}
代码与测试
package datastructure.tree.unionfind;
public class UnionFind {
/**
* parents 数组,parents[i]代表节点i的父亲节点。
* 如果parents[i]=k,代表i和k是连通的。
*/
int[] parents;
/** 并查集构造函数,n代表总共几个节点
* @param n
*/
public UnionFind(int n){
parents=new int[n];
for(int i=0;i<n;i++){
// 最开始每个节点i只和它自己连通
parents[i]=i;
}
}
/** 连通节点a和节点b。
* 如果a和b原来不连通,把a的root放到b树的root下面,即b的root为总的根节点。
* @param a
* @param b
*/
public void connect(int a,int b){
if(find(a)==find(b)){
// 如果a和b的根是同一个,说明a与b已经相连,不用操作。
return;
}
// 把a的root放到b树的root下面
parents[find(a)]=parents[find(b)];
}
/** 返回节点i的根节点编号,并且把i到根节点的所有节点的父节点,都变成根节点,进行了路径压缩
* @param i
* @return
*/
public int find(int i){
if(parents[i]==i){
return i;
}
// 将i的父亲节点变为i这个树的根节点(由于递归操作,i到根节点的所有节点的父节点,都变成根节点)
return parents[i]=find(parents[i]);
}
public void showRoot(){
System.out.println();
for(int i=0;i<parents.length;i++){
System.out.print(parents[i]+" ");
}
System.out.println();
for(int i=0;i<parents.length;i++){
System.out.println(i+" "+find(i));
}
}
}
package datastructure.tree.unionfind;
public class Main {
public static void main(String[] args) {
UnionFind unionFind=new UnionFind(5);
unionFind.showRoot();
unionFind.connect(0, 2);
unionFind.connect(1, 3);
unionFind.connect(4, 0);
unionFind.connect(0, 3);
unionFind.showRoot();
}
}