Java算法--并查集(Union-Find)
参考资料(通俗易懂):http://blog.csdn.net/dellaserss/article/details/7724401/
参考资料:http://blog.csdn.net/dm_vincent/article/details/7655764
概述
并查集(Disjoint set或者Union-find set)是一种树型的数据结构,常用于处理一些不相交集合的合并及查询问题。
基本操作
并查集是一种非常简单的数据结构,它主要涉及两个基本操作,分别为:
合并两个不相交集合:union
判断两个元素是否属于同一个集合:connected
下文中并查集均用数组来表示,数组下标表示元素,数组的下标对应的元素表示分组或者付节点。
并查集的连接:

首先说明四种方法的时间复杂度
| 初始化 | 合并 | 查找 | |
| Quick-find | N | N | 1 |
| Quick-union | N | N(最差) | 1 |
| weighted-QU | N | lgN(最差) | lgN |
| Weighted Quick-Union With Path Compression | N | lgN(?) | lgN |
0. main函数:
public static void main(String args[]){ int N=10; UF uf = new UF(N); while(!StdIn.readInt()){ int p = StdIn.readInt(); int q = StdIn.readInt(); if(!uf.connecten(p,q)) uf.union(p,q); } }
1. Quick-Find
并查集用数组来表示,数组下标表示元素,数组的下标对应的元素表示分组。
连接p、q的时候,将与pid相同的元素id全改为qid,当所有id均相同时,连接完成
package UnionFind; public class QuickFindUF { private int[] id; public QuickFindUF(int N ){ id = new int[N]; for(int i = 0 ; i < N ; i++) id[i]=i; } public boolean connected(int p , int q ){ return id[p]==id[q]; } //连接p、q的时候,将与pid相同的元素id全改为qid,当所有id均相同时,连接完成 public void union(int p , int q){ int pid = id[p]; int qid = id[q]; for(int i = 0 ; i <id.length ; i++) if(id[i]==pid) id[i]=qid; } }
解释:
比如输入的Pair是(5, 9),那么首先通过connected方法发现它们的组号并不相同,然后在union的时候通过一次遍历,将组号1都改成8。

上述代码的,因为仅仅需要一次数组读取操作就能够找到该节点的组号。但是,对于需要添加新路径的情况(union),需要对组号进行修改,必须对整个数组进行遍历,找到需要修改的节点,逐一修改。每次添加新路径带来的复杂度就是线性关系了,若需要添加的连接数量为n,那么union算法时间复杂度为平方阶。
2. Quick-Union
上面算法时间复杂度高是因为每个元素都需要记录一个组号,当合并的时候需要查询每个元素。因此,最好可以将同组的元素放在一起,可以使用tree
仍然使用数组存放元素。数组下标表示元素,对应下标存放的数值表示元素指向的父节点。

package UnionFind; public class QuickUnion { private int[] id; public QuickUnion(int N ){ id = new int[N]; for(int i = 0 ; i < N ; i++) id[i]=i; } //寻找父指针直到到达根,根节点特点:i==id[i] private int root(int i ){ while(i!=id[i]) i = id[i]; return i; } private boolean connected(int p , int q){ return root(p)==root(q); } //合并P,Q 就是把p的root的id设置为q的root的id private void union(int p , int q){ int i = root(p); int j = root(q); id[i]=j; } }
3. Weighted-quick-union
上面的算法存在一个问题,最坏情况下,当连接的时候将大树连接到小树上时,最终可能形成一颗十分高的树,这样求root会慢很多。
改进:要避免大树下降,先考虑树的大小,每次让小树连接到大树上,从而保持树的平衡。
创建一个额外的数组int sz[]; 初始元素都置为1。
合并时先判断树大小,将小树合并到大树上,然后改变大树的sz[]
package UnionFind; public class QuickUnionWeighted { private int[] id; private int[] sz; public QuickUnionWeighted(int N ){ id = new int[N]; for(int i = 0 ; i < N ; i++){ id[i]=i; sz[i]=1; } } //寻找父指针直到到达根 private int root(int i ){ while(i!=id[i]) i = id[i]; return i; } private boolean connected(int p , int q){ return root(p)==root(q); } //改进 //合并P,Q 就是把节点数多的root的id设置为节点数少的root的id private void union(int p , int q){ int i = root(p); int j = root(q); if(i==j) return; if(sz[i]<sz[j]){ id[i]=j; sz[j]+=sz[i]; }else{ id[j]=id[i]; sz[i]=sz[j]; } } }
4. Weighted Quick-Union With Path Compression
即对于Quick-Union算法而言,节点组织的理想情况应该是一颗十分扁平的树,所有的孩子节点应该都在height为1的地方,即所有的孩子都直接连接到根节点。这样的组织结构能够保证find操作的最高效率。
算法中有一个root函数用于寻找根节点,如果保存所有路过的中间节点到一个数组中,然后在while循环结束之后,将这些中间节点的父节点指向根节点,即可。但因为find操作的频繁性,会造成频繁生成中间节点数组,分配销毁的时间自然就上升了。选取一个折中的方法,将节点的父节点指向该节点的爷爷节点,相当于在寻找根节点的同时,对路径进行了压缩,使整个树结构扁平化。相应的实现如下,实际上只需要添加一行代码:
改进:
//寻找父指针直到到达根 private int root(int i ){ while(i!=id[i]){ //在寻找root的过程中顺便将i的父节点设置为它的爷爷节点。 id[i]=id[id[i]]; i = id[i]; } return i; }

浙公网安备 33010602011771号