并查集算法
并查集算法
参考文档:
https://baike.baidu.com/item/%E5%B9%B6%E6%9F%A5%E9%9B%86/9388442?fr=aladdin
https://www.cnblogs.com/gzh-red/p/11011539.html
https://baijiahao.baidu.com/s?id=1651803445417553212&wfr=spider&for=pc
https://zhuanlan.zhihu.com/p/93647900/ 前排警告
还债,以前上学的时候数据结构与算法经常逃课,结果现在又要回来自己学还没老师带(后悔药吃不下了),所以说出来混迟早要还。
算法本身就是很枯燥的,更多的都是根据例子自己来体会。本文将会多写一些例子。
因为是一边做题一边来写。所以这篇文章应该会写的比较久。 抛砖引玉
借用参考博客中的例子:
在江湖上有很多门派,这些门派相互争夺武林霸主。毕竟是江湖中人,两个人见面一言不合就开干。但是打归打,总是要判断一下是不是自己人,免得误伤。于是乎,分了各种各样的门派,比如说张无忌和杨过俩人要打架,就先看看是不是同一门派的,不是的话那就再开干。要是张无忌和杨过觉得俩人合得来,那就合并门派。而且规定了,每一个门派都有一个掌门人,比如武当派就是张三丰。华山派就是岳不群等等。
如何解决呢?这就涉及到现在要学习的并查集算法了 并查集特点
并查集,它的维护对象是我们的关注点。并查集适合维护具有 非常强烈的传递性质,或者是连通集合性质. 传递性质:传递性,也就是具有传递效应的性质,比如说A传递给B一个性质或者条件,让B 同样拥有 了这个性质或者条件,那么这就是我们所说的传递性. 连通集合性质:连通集合性,和数学概念上的集合定义是差不多的, 比如说A和B同属一个集合,B和C同属一个集合,那么A,B,C 都属于同一个集合 .这就是我们所谓的连通集合性质: 并查集抽象简单抽象
抽象就要对上面的问题进行分析,根据实现思路我们就可以抽象出来步骤和方法。
既然开干前要确认门派,又门派仅有一个掌门人。那么我们只要找到这俩个人对应的掌门就可以判断出这个俩个人是否是一个门派的了。 数据初始化 集合的合并 查找操作
下面是抽象的简单模型。主要是用来说明下这个思路,在真正处理的时候会有比较多的细节问题,这个后面进行完善。
这里数据存储采用的数组当然也可以采用其他方式。
1、江湖开始的时候,每个人都是自成一派的,也就是每一个江湖人的上级都是他自己。即自己是自己的掌门(master[index] = -1)
2、后来大家觉得一个人行走江湖太难于是就互相结交(认大哥,master[index] = x),每个人认一个大哥,不会认自己小弟的小弟为大哥(否则就乱了)。
3、查找一个人对应的掌门,就找这个人的大哥的大哥的大哥…(递归) package com.jmmq.load.jim.algorithm.dsu; import java.util.Arrays; public class DsuSet { // 掌门数组 -记录每一个人的对应的掌门 private int[] master; // 总人数 private int count; /** * 初始化 并查集(dsu) */ public DsuSet(int num) { master = new int[num]; count = num; // -1 表示自己即为掌门, Arrays.fill(master, -1); } /** * 合并操作 - 指定 low 的上级为 uplev */ public void unionSet(int low, int upLev){ master[low] = upLev; } /** * 查找操作 * 递归 */ public int findMaster(int ele) { if (master[ele] < 0) { return ele; } else { return findMaster(master[ele]); } } } 优化
首先最明显地查找里面出现了递归,那么递归越少越好。不要让递归太多。也就是说,我们尽量让一组人都认一个人为大哥,这样查找的时候仅需要查找一次就可以。
上面的想法在这里可以称为路径压缩: 这类问题最后的结构可以理解为几个集合组成一棵树。
我们在查询过程中只关心根结点是什么,并不关心这棵树的形态(有一些题除外)。因此我们可以在查询操作的时候将访问过的每个点都指向树根,这样的方法叫做路径压缩,单次操作复杂度为O(logN)。
改进后的代码如下: package com.jmmq.load.jim.algorithm.dsu; import java.util.Arrays; public class DsuSet1 { // 掌门数组 -记录每一个人的对应的掌门 private int[] master; // 总人数 private int count; /** * 初始化 并查集(dsu) */ public DsuSet1(int num) { master = new int[num]; count = num; // -1 表示自己即为掌门, Arrays.fill(master, -1); } /** * 合并操作 - 指定 low 的上级为 uplev */ public void unionSet(int upLev1, int upLev2){ // master[low] = upLev; // 合并的时候,判断一下root1和root2谁的子节点多, // 谁多谁做上级领导。就好比是两个人见面合并,谁的人数,谁做大哥。 if(findMaster(upLev1) == findMaster(upLev2)){ return; } if(master[upLev1] < master[upLev2]){ master[upLev1] = upLev2; } else { if (master[upLev1] == master[upLev2] || master[upLev1] > 0) { master[upLev1]--; } } } /** * 查找操作 * 递归 */ public int findMaster(int ele) { if (master[ele] < 0) { return ele; } else { // return findMaster(master[ele]); // 路径压缩 return master[ele] = findMaster(master[ele]); } } } 练习题目
下面并非是按照答题网站上的格式编写的。例子不能保证完全对,主要是理解算法的思路。
当然这里的练习题目也有很多或者要结合其他算法,或者使用其他算法也可以实现的。至于其他算法我后续学习的时候会来总结 第一题描述
有 n个同学(编号为 1 到 n )正在玩一个信息传递的游戏。在游戏里每人都有一个固定的信息传递对象,其中,编号为 i 的同学的信息传递对象是编号为 Ti的同学。
游戏开始时,每人都只知道自己的生日。之后每一轮中,所有人会同时将自己当前所知的生日信息告诉各自的信息传递对象(注意:可能有人可以从若干人那里获取信息, 但是每人只会把信息告诉一个人,即自己的信息传递对象)。当有人从别人口中得知自己的生日时,游戏结束。请问该游戏一共可以进行几轮?
输入格式 :
共2行。
第1行包含1个正整数 n ,表示 n个人。
第2行包含 n 个用空格隔开的正整数 T1,T2,⋯⋯,Tn ,其中第 i 个整数 Ti 表示编号为 i 的同学的信息传递对象是编号为 Ti 的同学, Ti≤n 且 Ti≠i。
输出格式 :
1个整数,表示游戏一共可以进行多少轮。
输入样例 :
5
2 4 2 3 1
输出样例
3
说明
当进行完第3 轮游戏后, 4号玩家会听到 2号玩家告诉他自己的生日,所以答案为 3。当然,第 3 轮游戏后,2号玩家、 3 号玩家都能从自己的消息来源得知自己的生日,同样符合游戏结束的条件。
对于 30%的数据, n≤200n;
对于 60%的数据, n≤2500;
对于100的数据, n≤200000。
如果一个人可以得到自己的生日信息,那这个人和传递信息的人肯定处于同一个集合中。对于这道题目我们不关心每一个人手上持有的信息,而是关心哪些人凑成了最小的集合(集合越小轮数越小)
当然解决这个问题的算法有很多比如:拓扑序,Tarjan,基环树等等(这些算法后面进行学习)。当然也可以使用并查集。 package com.jmmq.load.jim.algorithm.dsu; import java.util.Arrays; import java.util.Scanner; public class DsuPrc1 { // 100%的数据, n≤ 200000 , 存储父节点 public static int[] master; // 玩家集合 public static int[] players; // 最小集合计数器 public static int count; public static void main(String[] args) { Scanner sc = new Scanner(System.in); // 键盘输入 5 int n = 5; // 键盘输入数组 2 4 2 3 1 players = new int[n+1]; players[1] = 2; players[2] = 4; players[3] = 2; players[4] = 3; players[5] = 1; // 初始化 - 上级为自己(自己父节点是自己) -1 表示自己为父节点 master = new int[n+1]; Arrays.fill(master, -1); // 上一个人作为下一个人的父节点, for(int i=1; i count ? ((count > 1) ? count: 200002) : ans; } } // 没有重复则等于n if(ans == 200002){ System.out.println(n); } System.out.println(ans); } /** * 合并 * @param pre * @param afer */ public static void unionInfo(int pre, int afer){ master[afer] = pre; } /** * 查找 * @param x * @return */ public static int find(int x){ count++; // System.out.println(x); if(master[x] == -1){ return x; } else { return find(master[x]); } } }
master[] 表示父级列表(存储的是父级列表,若等于-1表示父级为自己)
player[] 表示参加的每一个人传递的下个元素。
n 表示人数。根据题意是不会出现第一轮就结束的。
|—————|±----+———+———+———+———+———|
| ①master[]| x | -1 | -1 | -1 | -1 | -1 |
|——————— ——— ——— ——— ——— ——— ——|
| ②master[]| x | -1 | 1 | 2 | 3 | 4 |
|—————|±----+———+———+———+———+———|
| players[] | | 2 | 4 | 2 | 3 | 1 |
|—————|+ +———+———+———+———+———|
| index | 0 | 1 | 2 | 3 | 4 | 5 |
|————————+———+———+———+———+———| 第二题
后面的题目都写在代码注释上了
这个题和 抛砖引玉 里的例子差不多。
给人感觉更简单一些,因为每次给出的2个数字完全可以找一下是否有父级,如果没有就自己是父级,这样就可以将所有朋友归于同一个父级。然后查父级一致的数量最大值即可。 package com.jmmq.load.jim.algorithm.dsu; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * 1920年的芝加哥,出现了一群强盗。如果两个强盗遇上了,那么他们要么是朋友,要么是敌人。而且有一点是肯定的,就是: * **我朋友的朋友是我的朋友;** * **我敌人的敌人也是我的朋友。** * 两个强盗是同一团伙的条件是**当且仅当他们是朋友**。现在给你一些关于强盗们的信息,问你最多有多少个强盗团伙。 * * **输入输出格式** * * **输入格式**: * 输入的第一行是一个整数N(2≤N≤1000)(2≤N≤1000),表示强盗的个数(从1编号到N)。 第二行M(1≤M≤5000)(1≤M≤5000),表示关于强盗的信息条数。 以下M行, * 每行可能是F p q或是E p q(1≤p,q≤N)(1≤p,q≤N), * F表示p和q是朋友, * E表示p和q是敌人。 * 输入数据保证不会产生信息的矛盾。 * **输出格式:** * * 输出只有一行,表示最大可能的团伙数。 * **输入输出样例** * 6 * 4 * E 1 4 * F 3 5 * F 4 6 * E 1 2 * * **输出样例** * 3 */ public class Dsuprc2 { public static List
许家印当年为什么不与贾跃亭一起为梦想窒息?许家印当年的那一波骚操作真的让人不解。他竟然投资入股了贾跃亭的汽车公司法拉第。那个喊出一起为梦想窒息的男人,别人都快窒息死掉了,他却跑到了国外,呼吸着美国香甜的空气!对于这种信誉不
三大一线品牌主板,怎么选微星华硕技嘉的主板哪个好三大品牌都属于一线品牌,非要说那个好,就不好比较了,毕竟三个品牌中各有高中低三个档次的定位。高档次的堆料足,用料好,做工上乘中端的相对也不错,低端的在保持基
让客厅变现场的捷径,就是装一套惠威音响随着智能大屏电视的普及,现在大家足不出户也能观看海量影片演唱会等等,无需奔赴人山人海,也能实现娱乐自由。不过,现在的大屏设备为了追求极致轻薄,腔体内再难放下尺寸大点儿的发声单元,在
从东芝阿尔斯通到华为,美国强取豪夺的套路有多深?从华为中兴被频频打压,孟晚舟被困加拿大,到TikTok微信被美国政府威胁封禁,在这个经济全球化的时代里,中国高新技术产业出海面临着诸多难以想象的困难。而美国对中国高科技企业的打压封
某鱼iPhone12只要2200元,是否敢买?会不会被套路网友今天看到一台iPhone12,居然还支持验货和邮寄,关键价格只要2200元!以前我们都知道,如果价格非常低,而且只能当面交易的,都是骗子。那这次又是什么套路呢?其实非常简单,他
电脑技巧大全1很多时候,需要暂时离开座位去做别的事情,如果对自己的电脑安全很重视,不妨按住windows键后,再按L键,这样电脑就直接锁屏了,这样就不用担心电脑的资料外泄啦2要找电脑上的文件时
老师傅的中肯建议若预算充足,建议一部到位选这4款高端手机很多人觉得旗舰机太贵,所以在买手机的时候一犹豫就买了千元机,觉得千元机也没什么不好的。实际上,千元机刚开始用的时候还算流畅,一旦时间长了就会,半年一年之后就开始卡顿闪退甚至黑屏,使
东数西算努力构建数字时代经济新版图5月24日拍摄的贵州省贵安新区华为云数据中心项目现场(无人机照片)。新华社记者欧东衢摄9月14日拍摄的贵州省贵安新区华为云数据中心。新华社记者刘续摄9月14日拍摄的贵州省贵安新区华
ipadmini6现场实感今天休息,趁早出发去了一趟最近的苹果零售店,Apple虹悦城。结果路上还是堵的要命,正常30分钟的路程愣是开了60分钟。去之前就有心理准备,估计是人山人海,到了门口,果不其然。排了
体验与iPhone13系列打得有来有回!iQOO8系列也挺香意料之中,此次iPhone13系列在发布之后又成为了各大平台的热门话题。从大家讨论的内容来看,有不少人对iPhone13系列的升级力度略感失望,觉得依旧是挤牙膏式升级,没有什么特别
红米K40游戏版和真我GTNeo能让联发科翻身吗?现实还是打脸了提起联发科,大家心里都怕怕。那句一核打架,九核围观的名言沥沥在目。今年,联发科重整旗鼓,想靠天玑1200芯片翻身,进军中高端市场,分一杯羹。那这颗被联发科寄予厚望的旗舰芯片能逆袭成