并查集及其算法实现

来源:互联网 发布:安庆2017网络效应答案 编辑:程序博客网 时间:2024/05/22 00:22

1、算法思想:转自:http://blog.csdn.net/hpuhjh/article/details/47832795

一、算法介绍:

并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。

 

并查集的基本操作有两个:

1:合并

union(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果两个集合相交则不合并。

2:查询

find(x):找到元素 x 所在的集合的代表,该操作常用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

 

并查集类似一个森林,是用树形结构来实现的,所以以下的讲解用树形结构来构造模型:

事先声明:

(1)一个集合对应一棵树。

(2)一个集合中的元素对应一个节点。

 

二、算法实现:

 

一、初始化:

我们用n个节点表示n个元素,有一点要特别注意:一个节点,若它的父节点等于它本身,则说明这个节点是根节点。

定义数组 per[], per[x]代表x的父节点。初始化时我们把per[x] = x,相当于每个节点都是独立的根节点(每个根节点都代表一个集合)

  1. //n 代表一共有n个元素(n个节点)  
  2. for(int i = 1; i <= n; ++i){  
  3.     per[i] = i;  
  4. }  

二、查找:

find(x),查找元素x所在的集合,即查找节点x在哪一棵树上,这里我们知道,在一棵树中,根节点是唯一的,父节点子节点都是相对而言的。要想确定节点x在哪一课树,我们只需要找到x的根节点就可以了。

如果判断节点x 和 节点y在不在同一棵树上,我们只需要找到x 和 y的根节点,若x和y的根节点相同,则x,y在同一颗树上,否则在不用的树上。

代码:

  1. int find(int x){      
  2.     int r = x;      
  3.     //父节点等于自身的节点才是根节点,      
  4.     //若 r 节点的父节点不是根节点,一直向上找。      
  5.    while(r != per[x])  
  6.         r = per[x];  
  7.     return r;  
  8. }  

 

1.1

如图1.1所示:

我们令节点1为根节点。查找节点4在哪一棵树,我们只要找到4的根节点就可以了。

查找过程: 找到4的父节点per[4]为2,不等于它本身,继续向上查找, 2它的父节点per[2]为1,不等于它本身,继续向上查找,1的父节点per[1]为1等于它本身,说明1是根节点。

这里有一个路径压缩的优化, 当我们查找到4的根节点为1时,我们直接将per[4] = 1,即直接把4连在根节点1上,而且在查找4时还会找到2,可能还有其他的节点,将这些节点的per[]通通都设置为1,这样下次再查找4的子节点所在的树时,查找次数就缩短了1.

 

这里压缩路径有两种写法,一种是递归的,一种是非递归的。

 

1> 递归:

  1. int find(int x){  
  2.     while(x == per[x])  
  3.         return x;  
  4.     return per[x] = find(per[x]);  
  5. }  

2>非递归

  1. int find(int x){      
  2.     int r = x;      
  3.     while(r != per[x])          
  4.         r = per[x];      
  5.     int i = x, j;      
  6.     while(i != r){          
  7.         j = per[i];          
  8.         per[i] = r;          
  9.         i = j;      
  10.     }      
  11.     return r;  
  12. }  

请读者自己模拟一下这两种压缩路径的方式不同之处在于:只是各个节点修改父节点的顺序不同而已。递归方法:先修改距离父节点近的节点,这由递归性质决定的;非递归方法:先修改离父节点最远的节点。


三、合并:

合并x 和 y所在的树, 只需要把其中一个树的根节点设置为令一个树根节点的子节点即可

 

  1. void union(int x, int y){      
  2.     int fx = find(x);//x的根节点为fx      
  3.     int fy = find(y);//y的根节点为fy      
  4.     if(fx != fy)          
  5.         per[fx] = fy;  
  6. }  

但是这里有一个问题, 是把 x的根节点设置为 y根节点的子节点,还是把y的根节点设置为x根节点的子节点。

节点1和节点2是一棵树,根节点为1, 节点3是一棵树,根节点是自身为3.

 

图1.2

 

 图1.3

如图1.2所示:

现在我们根节点3作为根节点1的子节点,此时查找2的根节点,只需要查找一次。

如图1,3所示 

现在我们根节点1作为根节点3的子节点,此时查找2的根节点,需要先找到1,再找到3,多了一次查找。

所以这里存在一种优化。我们可以设置一个数组rank[ ],用它来记录每一棵树的深度,合并时如果两棵树的深度不用,那么从深度(rank)小的向深度(rank)达的连边。(但注意,压缩路径时会使树的深度发生变化,但我们不修改rank 的值)

 

  1. int per[maxn];//记录父节点  
  2. int rank[maxn];//记录树的深度  
  3. void init(){//初始化n个节点      
  4.     for(int i = 1; i <= n; ++i){          
  5.         per[i] = i;          
  6.         rank[i] = 0;  
  7.     }  
  8. }  
  9. //找到根节点,压缩路径  
  10. int find(int x){      
  11.     if(x == per[x])          
  12.         return x;      
  13.     return per[x] = find(per[x]);  
  14. }  
  15.   
  16. void union (int a, int b){    
  17.     int fa = find(a);    
  18.     int fb = find(b);    
  19.     if(fb != fa){    
  20.         if(rank[fa]  < rank[fb]){    
  21.             per[fa] = fb;    
  22.         }    
  23.         else{    
  24.             per[fb] = fa;    
  25.             if(rank[fa] == rank[fb]) 
  26. rank[fa]++;    
  27.         }    
  28.     }      
  29. }    


2、牛客网刷题:

题目描述

亮亮解出了卷轴隐藏的秘密,来到了一片沼泽地。这里有很多空地,而面试直通卡可能埋在任意一块空地中,好在亮亮发现了一堆木材,他可以将木材铺在两个空地之间的沼泽地上。因为亮亮不知道面试直通卡具体在哪一块空地中,所以必须要保证任意一块空地对于亮亮来说是可以抵达的。 “怎么还有鳄鱼!没办法,看来有些空地不能直接到达了。” 亮亮虽然没有洁癖,但是沼泽地实在太臭了,所以亮亮不会循环利用木材。而且木材不能拼接在一起使用,所以亮亮必须要知道在耗费木材最少的情况下,最长的那根木材至少需要多长。

输入描述:

第一行包含两个整数N(1≤N≤10000),M(1≤M≤1000000)。N表示公有N块空地。接下来M行,每行包含两个整数P(1≤P≤N),Q(1≤Q≤N),K代表P,Q两个间没有鳄鱼,需要耗费K的木材。

输出描述:

一个整数,即耗费木材最少的情况下,最长的那根木材长度。
示例1

输入

4 31 2 12 3 13 4 2

输出

2



代码实现:参考:http://blog.csdn.net/lezg_bkbj/article/details/52127517


package schooloffer;import java.util.Arrays;import java.util.Comparator;import java.util.Scanner;/** * Created by caoxiaohong on 17/9/21. * 亮亮解出了卷轴隐藏的秘密,来到了一片沼泽地。这里有很多空地,而面试直通卡可能埋在任意一块空地中,好在亮亮发现了一堆木材,.... * 算法思想:并查集+最小生成树 * 求:最小生成树中:权值最大的那个路径 */public class SearchTreasure {    static class Edge{        private int x;        private int y;        private int weight;        Edge(int x,int y,int weight){            this.x=x;            this.y=y;            this.weight=weight;        }    }    public static void main(String[] args) {        Scanner scanner=new Scanner(System.in);        int N,M;        while (scanner.hasNext()){            String[] str=scanner.nextLine().split(" ");            N=Integer.valueOf(str[0]);            M=Integer.valueOf(str[1]);            Edge[] values=new Edge[M];            for(int i=0;i<M;i++){                String[] w=scanner.nextLine().split(" ");                int x=Integer.valueOf(w[0]);                int y=Integer.valueOf(w[1]);                int weight=Integer.valueOf(w[2]);                Edge edge=new Edge(x,y,weight);                values[i]=edge;            }            System.out.println(solution(values,N,M));        }    }    //并查集:初始化    private static void init(int[] father,int n){        for(int i=0;i<=n;i++)            father[i]=i;    }    //并查集:查找根节点    private static int getFather(int[] father,int x){        int r=x;        while (father[r]!=r) // father[x]=y:表示x点对应对根节点为y            r=father[r];        //压缩路径:        int i=x;        while (i!=r) {            int j=father[i];            father[i]=r;            i=j;        }        return r;    }    //并查集:合并    private static void mergeSet(int[] father,int fx,int fy){        if(fx>fy){            father[fx]=fy;        }else {            father[fy]=fx;        }    }    //总算法    private static int solution(Edge[] values,int n,int m){        int result=0;        //初始化        int[] father=new int[n+1];        init(father,n);        //对valuse[]按照weight排序        Arrays.sort(values, new Comparator<Edge>() {            @Override            public int compare(Edge o1, Edge o2) {                return o1.weight-o2.weight;            }        });        for(int i=0;i<m;i++){            int fx=getFather(father,values[i].x);            int fy=getFather(father,values[i].y);            if(fx!=fy){                if(result<values[i].weight)                    result=values[i].weight;                //合并节点                mergeSet(father,fx,fy);            }        }        return result;    }}


原创粉丝点击