并查集入门及例题分析 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kaolalicai
V2EX    Java

并查集入门及例题分析

  •  
  •   kaolalicai
    Kalengo 2019-06-08 18:40:18 +08:00 2764 次点击
    这是一个创建于 2395 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一、并查集的原理

    并查集 Union-Find )是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
    主要涉及两种操作:合并和查找。 具体地说,初始状态下,并查集中的元素是互不相交的,经过一系列操作(Union)后,合并成一个集合。 而在进行了某次合并之后,如果想知道:某两个元素是否已经处在同一个集合中了?这时候就需要查找( Find )操作。
    一张江湖解说图如下:

    简单来讲,这个过程可以理解为找祖宗和认祖宗的故事。如果此时你想找到杨左使的管理者,就可以用并查集结构,如果你想让宋远桥归属到张无忌门派,就可以使用到并查集结构。
    那并查集的初始与结构是怎么样的呢?

    二、并查集的数据结构及实现方式

    1、构建并初始化并查集

    初始化并查集结构,用上面图来说,就是先初始有多少个江湖人士,然后默认他们都是各自一派的管理员。代码如下:

    class findUnionCollection{ private int[] s; // 江湖人数的数组集 int count; // 门派个数 // 构建江湖人士的数组集和门派个数 public findUnionCollection(int length){ s = new int[length]; s = initCollection(s); count = length; } // 将每个江湖人士设置为各自一派的管理者(根节点默认为-1) private int[] initCollection(int[] target){ for (int i =0;i<target.length;i++){ target[i]=-1; } return target; } } 

    有了各个门派的一个集合,此时我们应该也要有个可以找到每个门派的管理者一个查找方法。

    代码讲解:

    public int find(int x){ // 当这个成员的所对应的值小于0,即为-1,则为门派的管理员,直接返回 if(s[x]<0){ return x; } // 如果不为0,则这个成员所对应的值是他的直接上层大佬 // 此时可以去递归这个大佬的大佬,直到找到最终的管理员 return find(s[x]); } 

    当然,这里有个优化点,就是我们这边只是关心这个门派的管理员,而不关心这个门派的各个层次的关系。所以我们在查询的时候,可以将这层直接指引到管理员,方便下次直接查找,术语简称路径压缩。
    代码如下:

    public int find(int x){ // 当这个成员的所对应的值小于0,即为-1,则为门派的管理员,直接返回 if(s[x]<0){ return x; } // 如果不为0,则这个成员所对应的值是他的直接上层大佬 // 此时可以去递归这个大佬的大佬,直到找到最终的管理员 // 将各个门派的成员都指向最终管理员(实际就是路径压缩) return s[x] = find(s[x]); } 

    有了找祖宗的方法,当然也少不了认祖宗的方法,换江湖的说话,就是投靠另一门派

    代码解析:

    // 合并两个江湖人士所属门派 public void unionCollection(int child1,int child2){ // 先判断两个江湖人士是否所属一派,如果是,直接返回,无须合并 if(find(child1)==find(child2)){ return; } // 之后我们这个成员所属门派的管理员的深度进行对比 // 如果两个管理者的深度一样,就随机选一个管理员作为最终管理员,将加深其深度(减1) // 如果 child1 的深度比 child2 的深度深,则将 child2 的管理员指向 child1 管理员 if(s[find(child1)]<s[find(child2)]){ s[find(child2)]=find(child1); }else { if(s[find(child1)]==s[find(child2)]){ s[find(child2)]--; } s[find(child1)]=find(child2); } 由于每一次合并,都会减少一个门派,所以门派的数量也就递减 count--; } 

    三、例题实战

    1、题目描述

    班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。

    给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果 Mi = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。

    示例 1:

    输入:

    [[1,1,0],

    [1,1,0],

    [0,0,1]]

    输出: 2

    说明:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。

    第 2 个学生自己在一个朋友圈。所以返回 2。

    示例 2:

    输入:

    [[1,1,0],

    [1,1,1],

    [0,1,1]]

    输出: 1

    说明:已知学生 0 和学生 1 互为朋友,学生 1 和学生 2 互为朋友,所以学生 0 和学生 2 也是朋友,所以他们三个在一个朋友圈,返回 1。

    注意:

    N 在[1,200]的范围内。

    对于所有学生,有 Mi = 1。

    如果有 Mi = 1,则有 Mj = 1。

    2、解题思路

    1 )先构建并初始一个长度为 N 的并查集数组,此时朋友圈的数量初始的长度;

    2 )遍历 M 二维数组,判断每个学生之间的关系是否为 1,为 1 则调合并操作将两者关联起来,同时对朋友圈的数量递减;

    3 )遍历完则直接返回朋友圈的数量。

    3、代码实例:

    class findUnionCollection{ private int[] s; // 江湖人数的数组集 int count; // 门派个数 // 构建江湖人士的数组集和门派个数 public findUnionCollection(int length){ s = new int[length]; s = initCollection(s); count = length; } // 将每个江湖人士设置为各自一派的管理者(根节点默认为-1) private int[] initCollection(int[] target){ for (int i =0;i<target.length;i++){ target[i]=-1; } return target; } public int find(int x){ // 当这个成员的所对应的值小于0,即为-1,则为门派的管理员,直接返回 if(s[x]<0){ return x; } // 如果不为0,则这个成员所对应的值是他的直接上层大佬 // 此时可以去递归这个大佬的大佬,直到找到最终的管理员 // 将各个门派的成员都指向最终管理员(实际就是路径压缩) return s[x] = find(s[x]); } } public void unionCollection(int child1,int child2){ // 先判断两个江湖人士是否所属一派,如果是,直接返回,无须合并 if(find(child1)==find(child2)){ return; } // 之后我们这个成员所属门派的管理员的深度进行对比 // 如果两个管理者的深度一样,就随机选一个管理员作为最终管理员,将加深其深度(减1) // 如果 child1 的深度比 child2 的深度深,则将 child2 的管理员指向 child1 管理员 if(s[find(child1)]<s[find(child2)]){ s[find(child2)]=find(child1); }else { if(s[find(child1)]==s[find(child2)]){ s[find(child2)]--; } s[find(child1)]=find(child2); } 由于每一次合并,都会减少一个门派,所以门派的数量也就递减 count--; } class Solution { public Solution(){ } public static int findCircleNum(int[][] M) { int studentNums = M[0].length; findUnionCollection FUC = new findUnionCollection(studentNums); for(int i=0;i<studentNums-1;i++){ for(int j = i+1; j<M[i].length;j++){ if(M[i][j]==1){ FUC.unionCollection(i,j); } } } return FUC.getCount(); } public static void main(String[] var0) { int[][] M = new int[][]{{1,1,0},{1,1,0},{0,0,1}}; System.out.println("findUnionCollection:"+findCircleNum(M)); } } 
    1 条回复    2019-06-08 19:35:01 +08:00
    kj1
        1
    kj1  
       2019-06-08 19:35:01 +08:00 via Android
    初一就学并查集,当时不是这个翻译的,当时叫 disjoint set, 不过决赛很少用
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5481 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 07:36 PVG 15:36 LAX 23:36 JFK 02:36
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86