首页 > 技术文章 > Union/find--不相交集类(并查集)

CongLollipop 2017-03-22 15:51 原文

  不相交集类 ,可以用来解决等价问题,实现起来简单,可以只使用一个数组。

用一个数组id[N]  记录N个触点,初始化Id[i] =i;

实现动态链接的时候,遍历这个数组,如果p和q的id[p] =id[q] 那么不用管,否则要将pq链接起来,使用uion方法,也就是将p的分量重命名为id[q]。

具体实现如下:

package p2;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class UnionFind {
    public int [] id;
    public int count;
    
    
    public UnionFind(int N){ //初始化数组  表示有N个触点
        
        count =N;
        id = new int[N];
        for(int i=0;i<N;i++){
            id[i] =i;
        }
    }
    /**
     * 
     * @param p
     * @return
     */
    public int find(int p){
        
        return id[p];
    }
    
    public void union(int p,int q){
        int pid=find(p);
        int qid = find(q);
        if(pid==qid) return ;
        //将所有被标为p的分量的 标为Q
        for(int i=0;i<id.length;i++){
            if(id[i] == pid) id[i] =qid;
        }
        count--;
    }
    /**
     * 若果pq是联通的就返回true
     * @param p
     * @param q
     * @return 
     */
    public boolean connected(int p,int q){
        
        return find(p)==find(q);
    }

    /**
     * 返回连通分量的数目
     * @return 
     */
    public int  count(){
        
        return count;
    }
    
    
    public static void main(String [] args){
        try {
            Scanner sc = new Scanner(new File("fortest"));
            int N =sc.nextInt();
            //System.out.println(N);
            UnionFind uf = new UnionFind(N);
            while(sc.hasNextLine()){
                int p = sc.nextInt();
                int q =sc.nextInt();
                if(uf.find(p)==uf.find(q)) continue;
                uf.union(p, q);
                System.out.println("建立连接"+p+"和"+q);
            }
            System.out.println(uf.count+"还没有连接上的components");
            System.out.println(uf.connected(0, 9));
         
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        
        
        
    }
}

测试集fortest如下:

10
4 3
3 8
6 5 
9 4
2 1
8 9
5 0
7 2
6 1
1 0
6 7
7 8

以上内容参考算法 第四版。

 

这种是最简单的quik-find,但是查找效率不高,需要遍历整个数组。后面会介绍quick-union 森林表示以及加权的quick-union.

顺便写一个quick-find 的应用题。修公路的题目盗用同学的博客题的图。哈哈

  用java实现思路就是用unionfind  N 个村庄,就是N个触点,从1-N 现在连通的有M条路,用A和B表示,那么如果需要将所有的村庄连通,则就是说这些村庄必须两两相连。那么首先,我们将已知的连通数记录下来,然后再将相连的子块标记,count,最后-1就是需要多修的公路数。利用以上的API,代码如下:

package p2;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class UnionFind {
    public int [] id;
    public int count;
    
    
    public UnionFind(int N){ //初始化数组  表示有N个触点
        
        count =N;
        id = new int[N+1];
        for(int i=1;i<=N;i++){
            id[i] =i;
        }
    }
    /**
     * 
     * @param p
     * @return
     */
    public int find(int p){
        
        return id[p];
    }
    
    public void union(int p,int q){
        int pid=find(p);
        int qid = find(q);
        if(pid==qid) return ;
        //将所有被标为p的分量的 标为Q
        for(int i=0;i<id.length;i++){
            if(id[i] == pid) id[i] =qid;
        }
        count--;
    }
    /**
     * 若果pq是联通的就返回true
     * @param p
     * @param q
     * @return 
     */
    public boolean connected(int p,int q){
        
        return find(p)==find(q);
    }

    /**
     * 返回连通分量的数目
     * @return 
     */
    public int  count(){
        
        return count;
    }
    
    
    public static void main(String [] args){
         int ans=0;
        try {
            Scanner sc = new Scanner(new File("fortest"));
            
            int N =sc.nextInt();
            int mark [] = new int[N+1];
            //System.out.println(N);
            int M = sc.nextInt();
            UnionFind uf = new UnionFind(N);
            while(sc.hasNextLine()){
                int p = sc.nextInt();
                int q =sc.nextInt();
                //if(uf.find(p)==uf.find(q)) continue;
                uf.union(p, q);
                System.out.println("建立连接"+p+"和"+q);
            }
            for(int i=1;i<=N;i++){  //标记连通块的节点
                mark[uf.find(i)] =1;
            }
            for(int i=1;i<=N;i++){
                if(mark[i]!=0) ans++;
            }
            System.out.println("还需要修"+(ans-1));
            
         
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        
        
        
    }
}

 

推荐阅读