部分文章内容为公开资料查询整理,原文出处可能未标注,如有侵权,请联系我,谢谢。邮箱地址:gnivor@163.com ►►►需要气球么?请点击我吧!

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算法而言,节点组织的理想情况应该是一颗十分扁平的树,所有的孩子节点应该都在height1的地方,即所有的孩子都直接连接到根节点。这样的组织结构能够保证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;
    }

 

posted @ 2015-04-27 10:33  流了个火  阅读(183)  评论(0)    收藏  举报
►►►需要气球么?请点击我吧!►►►
View My Stats