找亲戚——并查集

再次谈起什么是并查集?

在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:

Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
Union:将两个子集合并成同一个集合。
由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。
为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x)Find(x) 返回 xx 所属集合的代表,而 Union 使用两个集合的代表作为参数。

并查集概念

在一些应用问题中,需要将n个不同的元素划分成一些不相交的集合。开始时,每个元素自成一个单元素集合,然后按一定的规律将归于同一组元素的集合合并。在此过程中要反复用到查询某一个元素归属于那个集合的运算。适合于描述这类问题的抽象数据类型称为并查集(union-findset)。

 

并查集经典案例——找亲戚

问题描述:                                              
    给出总人数n和k对关系,然后给出p次关系询问                           
                                                      
输入说明:                                                 
    第一行输入两个正整数,分别代表总人数和关系数                           
    下面k行代表k对关系                                       
    再下面一行输入一个正整数p,                                   
    下面p行代表p次关系询问                                     
                                                      
输出说明:                                                 
    输出p行,每一行代表1次询问的回答,若有亲戚关系,则输出yes,反之输出no           
                                                      
输入示例1:                                                
9 7                                              
     2 4                                              
     5 7                                              
     1 3                                              
     8 9                                              
     1 2                                              
     5 6                                              
     2 3                                              
     3                                                
     1 9                                              
     5 6                                              
     4 8                                              
                                                      
输出示例1:                                                
    no                                               
    yes                                              
    no      

       

案例代码

 1 public class Main {
 2     
 3     /**
 4      * 查询
 5      */
 6     public static int find(int x, int[] parent) {
 7     if (parent[x] == x) {
 8         // 直接返回
 9         return parent[x];
10     }
11     // 递归找祖先
12     // 将当前节点的父亲设置为祖先,可以为之后的查询减少递归层数,提高算法的执行效率
13     // 关键代码
14     parent[x] = find(parent[x], parent);
15     return parent[x];
16     }
17     
18     /**
19      * 合并
20      */
21     public static void union(int a, int b, int[] parent) {
22     // 各自找祖先
23     int parent_a = find(a, parent);
24     int parent_b = find(b, parent);
25     // 将a祖先的祖先设置为b的祖先
26     parent[parent_a] = parent_b; 
27     }
28     
29     /**
30      * 初始化
31      */
32     public static void init(int[] parent) {
33     for (int i = 0; i < parent.length; i++) {
34         parent[i] = i;
35     }
36     }
37     
38     public static void main(String[] args) {    
39     // 设定人数,设定亲戚关系数
40     Scanner input = new Scanner(System.in);
41     int n = input.nextInt();
42     // 一开始,初始化自己就为自己的祖先,因为这里我设置人的编号从1~n,所以数组长度要开n+1
43     int[] parent = new int[n + 1];
44     init(parent);
45     int k = input.nextInt();
46     for (int i = 0; i < k; i++) {
47         // 合并
48         int a = input.nextInt();
49         int b = input.nextInt();
50         union(a, b, parent);
51     }
52     
53     // 接收询问次数
54     int p = input.nextInt();
55     List<String> list = new ArrayList<>();
56     for (int i = 0; i < p; i++) {
57         int a = input.nextInt();
58         int b = input.nextInt();
59         // 分别找各自祖先
60         int parent_a = find(a, parent);
61         int parent_b = find(b, parent);
62         if (parent_a == parent_b) {
63         list.add("yes");
64         } else {
65         list.add("no");
66         }
67     }
68     
69     for (int i = 0; i < list.size(); i++) {
70         System.out.println(list.get(i));
71     }
72     }
73 }

运行结果

posted @ 2021-04-13 12:23  没有你哪有我  阅读(172)  评论(0编辑  收藏  举报