目录

并查集简介

建立并查集(初始化)

建立关系

查找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();
	}

}