并查集详解:UF——UF_Tree——UF_Tree_Weighted逐步优化
目录
- 1 并查集简介
- 2 UF
- 2.1 UF(int N)构造方法实现
- 2.2 union(int p, int q)合并方法实现
- 2.3 代码实现(Java)
- 2.4 应用举例与复杂性分析
- 3 UF_Tree
- 3.1 算法优化
- 3.2 find(int p)查询方法实现
- 3.3 union(int p, int q)合并方法实现
- 3.4 代码实现(Java)
- 3.5 优化后的复杂性分析
- 4 UF_Tree_Weighted
- 4.1 路径压缩与复杂性分析
- 4.2 代码实现(Java)
- 5 案例-畅通工程
- 5.1 题目要求
- 5.2 解题思路
- 5.3 代码实现(Java)
1 并查集简介
并查集是一种树型的数据结构,它可以高效地进行如下操作:
- 查询元素p和元素q是否属于同一组;
- 合并元素p和元素q所在的组。
特点:
- 每个元素都唯一对应一个结点;
- 每一组数据中的多个元素都在同一个树中;
- 一组中的数据对应的树和另外一组中的数据对应的树之间没有任何联系;
- 元素在树中没有子父级关系的硬性要求。
2 UF
2.1 UF(int N)构造方法实现
- 初始情况下,每个元素都在一个独立的分组中,因此,初始情况下并查集中的数据分为N个组;
- 初始化数组eleAndGroup,索引是每个结点存储的元素,索引对应的值是该结点所在的分组,初始情况下,i索引处的值就是i。
2.2 union(int p, int q)合并方法实现
- 如果p和q已经在同一个分组中,无需合并;
- 如果p和q不在同一个分组,将p所在分组中所有元素的组标识符修改为q所在组的标识符,同时分组数量-1。
2.3 代码实现(Java)
public class UF {//记录结点元素和该元素所在分组的标识private int[] eleAndGroup;//记录并查集中数据的分组个数private int count;//初始化并查集public UF(int N){//初始化分组的数量,默认情况下,有N个分组this.count = N;//初始化eleAndGroup数组this.eleAndGroup = new int[N];//初始化eleAndGroup中的元素及其所在的组的标识符,让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在的组的标识符)等于该索引for (int i = 0; i < eleAndGroup.length; i++) {eleAndGroup[i] = i;}}//获取当前并查集中的数据有多少个分组public int count(){return count;}//元素p所在分组的标识符public int find(int p){return eleAndGroup[p];}//判断并查集中元素p和元素q是否在同一分组中public boolean connected(int p,int q){return find(p) == find(q);}//把p元素所在分组和q元素所在分组合并public void union(int p,int q){//判断元素q和p是否已经在同一分组中,如果已经在同一分组中,结束方法if (connected(p,q)){return;}//找到p所在分组的标识符int pGroup = find(p);//找到q所在分组的标识符int qGroup = find(q);//合并分组:让p所在组的所有元素的组标识符变为q所在分组的标识符for (int i = 0; i < eleAndGroup.length; i++) {if (eleAndGroup[i]==pGroup){eleAndGroup[i] = qGroup;}}//分组个数-1this.count--;}
}
public class Test {public static void main(String[] args) {Scanner sc = new Scanner(System.in);System.out.println("请录入并查集中元素的个数:");int N = sc.nextInt();UF uf = new UF(N);while(true) {System.out.println("请录入要合并的第一个点:");int p = sc.nextInt();System.out.println("请录入要合并的第二个点:");int q = sc.nextInt();// 判断p和q是否在同一分组if (uf.connected(p,q)) {System.out.println("结点" + p + "结点" + q + "已经在同一分组");continue;}uf.union(p, q);System.out.println("总共还有" + uf.count() + "个分组");}}
}
请录入并查集中元素的个数:
5
请录入要合并的第一个点:
1
请录入要合并的第二个点:
3
总共还有4个分组
请录入要合并的第一个点:
1
请录入要合并的第二个点:
3
结点1结点3已经在同一分组
请录入要合并的第一个点:
2.4 应用举例与复杂性分析
如果将并查集中存储的每个整数表示一台网络中的计算机,就可以通过connected(int p, int q)来检测网络中的两台计算机是否连通,也可以通过union(int p, int q)使p和q连通,以便通信。
如果让每个数据都连通,至少要调用N-1次union方法,而在union方法中使用了for循环,因此时间复杂度是O(N^2),如果要解决大规模问题,不太合适,需要优化。
3 UF_Tree
3.1 算法优化
对eleAndGroup数组的含义重新设定:
- 索引依然是每个结点存储的元素;
- 索引对应的值不再是当前结点所在的分组标识,而是该结点的父结点。
3.2 find(int p)查询方法实现
- 判断p的父结点eleAndGroup[p]是不是自己,如果是自己则证明已经是根节点了;
- 如果eleAndGroup[p]不是自己,令p=eleAndGroup[p],继续找父结点的父结点,直到找到根结点为止,最终的根结点即为元素所在的分组标识。
3.3 union(int p, int q)合并方法实现
- 找到p所在树的根结点;
- 找到q所在树的根结点;
- 如果p和q拥有同样的根结点,则无需合并;
- 否则将p的根结点的父结点设置为q的根结点;
- 分组数量-1.
3.4 代码实现(Java)
public class UF_Tree {//记录结点元素和该元素所在分组的标识private int[] eleAndGroup;//记录并查集中数据的分组个数private int count;//初始化并查集public UF_Tree(int N){//初始化分组的数量,默认情况下,有N个分组this.count = N;//初始化eleAndGroup数组this.eleAndGroup = new int[N];//初始化eleAndGroup中的元素及其所在的组的标识符,让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在的组的标识符)等于该索引for (int i = 0; i < eleAndGroup.length; i++) {eleAndGroup[i] = i;}}//获取当前并查集中的数据有多少个分组public int count(){return count;}//判断并查集中元素p和元素q是否在同一分组中public boolean connected(int p,int q){return find(p) == find(q);}//元素p所在分组的标识符public int find(int p){while(true){if (p == eleAndGroup[p]){return p;}p = eleAndGroup[p];}}//把p元素所在分组和q元素所在分组合并public void union(int p,int q){//找到p元素和q元素所在组对应的树的根结点int pRoot = find(p);int qRoot = find(q);//如果p和q已经在同一分组,则不需要合并了if (pRoot==qRoot){return;}//让p所在的树的根结点的父结点为q所在树的根结点即可eleAndGroup[pRoot] = qRoot;//分组数量-1this.count--;}
}
3.5 优化后的复杂性分析
如果让每个数据都连通,要调用N-1次union方法,但是union调用了find算法,UF的find时间复杂度为O(1),现在UF_Tree的find平均时间复杂度为O(N/2),因此连通所有的时间复杂度是O(N^2/2),比之前好,但依然有优化空间。
4 UF_Tree_Weighted
4.1 路径压缩与复杂性分析
- 之所以UF_Tree连通所有的时间复杂度是O(N^2/2),原因就在于合并过程中p所在树的深度一直在增加,最终达到N,导致find搜索根节点耗费了较多时间,如果让树的深度尽可能小,就可以降低find的时间复杂度,甚至为O(1)。
- 在UF_Tree中,union合并树的时候过于随意,如果我们把并查集中每一棵树的大小记录下来,然后在合并树的时候,将较小的树连接到较大的树上,就可以减小树的深度,甚至能降到2。
- 如此,UF_Tree_Weighted连通所有的时间复杂度为O(N)。
4.2 代码实现(Java)
为了能保证每次合并时,都能把小树合并到大树上,我们新增一个数组来记录每个根结点对应的树中元素的个数。
public class UF_Tree_Weighted {//记录结点元素和该元素所在分组的标识private int[] eleAndGroup;//记录并查集中数据的分组个数private int count;//用来存储每一个根结点对应的树中保存的结点个数private int[] sz;//初始化并查集public UF_Tree_Weighted(int N){//默认情况下,有N个分组this.count = N;//初始化eleAndGroup数组this.eleAndGroup = new int[N];//初始化eleAndGroup中的元素及其所在的组的标识符,让eleAndGroup数组的索引作为并查集的每个结点的元素,并且让每个索引处的值(该元素所在的组的标识符)等于该索引for (int i = 0; i < eleAndGroup.length; i++) {eleAndGroup[i] = i;}this.sz = new int[N];//默认情况下,sz中每个索引处的值都是1for (int i = 0; i < sz.length; i++) {sz[i] = 1;}}//获取当前并查集中的数据有多少个分组public int count(){return count;}//判断并查集中元素p和元素q是否在同一分组中public boolean connected(int p,int q){return find(p) == find(q);}//元素p所在分组的标识符public int find(int p){while(true){if (p == eleAndGroup[p]){return p;}p = eleAndGroup[p];}}//把p元素所在分组和q元素所在分组合并public void union(int p,int q){//找到p元素和q元素所在组对应的树的根结点int pRoot = find(p);int qRoot = find(q);//如果p和q已经在同一分组,则不需要合并了if (pRoot==qRoot){return;}//判断proot对应的树大还是qroot对应的树大,最终需要把较小的树合并到较大的树中if (sz[pRoot] < sz[qRoot]){eleAndGroup[pRoot] = qRoot;sz[qRoot] += sz[pRoot];}else{eleAndGroup[qRoot] = pRoot;sz[pRoot] += sz[qRoot];}//分组数量-1this.count--;}
}
5 案例-畅通工程
5.1 题目要求
- 某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇;
- 畅通工程的目标是使全省任何两个城镇间都可以实现交通(不一定直接相连,间接相连即可);
- 问最少还需建设多少条道路?
有文件traffic_project.txt,下面是文件内容及数据解释:
5.2 解题思路
- 创建一个并查集UF_Tree_Weighted(20);
- 分别调用union(0,1),union(6,9),uinon(3,8),union(5,11),union(2,12),union(6,10),union(4,8),表示已经相连的城市;
- 如果城市全部连接起来,并查集中的分组数目为1,所有城市都在一个树中,因此,只需获取当前并查集中的分组数目,减去1,即得到至少还需要修建的道路数。
5.3 代码实现(Java)
public class Traffic_Project {public static void main(String[] args) throws IOException {// 创建输入流BufferedReader reader = new BufferedReader(new FileReader("src/traffic_project.txt"));// 读取城市数目,初始化并查集int number = Integer.parseInt(reader.readLine());UF_Tree_Weighted uf = new UF_Tree_Weighted(number);// 读取已经修建好的道路数目int roadNumber = Integer.parseInt(reader.readLine());// 循环读取已经修建好的道路,并调用union方法for (int i=0; i<roadNumber; i++) {String line = reader.readLine();int p = Integer.parseInt(line.split(" ")[0]);int q = Integer.parseInt(line.split(" ")[1]);uf.union(p, q);}// 获取剩余的分组数量int groupNumber = uf.count();// 计算出还需要修建的道路System.out.println("还需要修建" + (groupNumber-1) + "条道路,城市才能相通");}
}
还需要修建12条道路,城市才能相通
并查集详解:UF——UF_Tree——UF_Tree_Weighted逐步优化相关推荐
- 并查集详解 ——图文解说,简单易懂(转)特别好玩
并查集详解 --图文解说,简单易懂(转) 2016年03月10日 17:38:08 阅读数:6931 标签: 并查集数据结构并查集算法图文解说 更多 个人分类: 算法--并查集 并查集是我暑假从高手那 ...
- - 并查集详解(第二节)
以下是并查集思路详解: 一:概念 并查集处理的是"集合"之间的关系.当给出两个元素的一个无序数对(a,b)时,需要快速"合并"a和b分别所在的集合,这期间需要反 ...
- 并查集详解(从引入到代码)
江湖上散落着各式各样的大侠,有上千个之多.他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架.但大侠们有一个优点就是讲义气,绝对不打自己的朋友.而且他们信奉&qu ...
- 7-3 最小生成树-kruskal (10 分)(思路+详解+并查集详解+段错误超时解决)宝 Come
一:前言 本题需要用到并查集的知识,建议先学完并查集后再看看本题 二:题目 题目给出一个无向连通图,要求求出其最小生成树的权值. 温馨提示:本题请使用kruskal最小生成树算法. 输入格式: 第一行 ...
- 拓扑排序 详解 + 并查集 详解 + 最小生成树详解
若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作! 本文为原创文章,转载请注明出处 本文链接 : http://www.cnblogs.com/Yan-C/p/3943940.htm ...
- 一个很有意思的并查集详解
并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...
- 深入浅出系列之——并查集详解【武侠版】【简单有趣】
一个超级有意思,好懂的并查集解释 故事读完,并查集就会了 故事开始了...留神! 江湖上散落着各式各样的大侠,有上千个之多.他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免 ...
- 并查集详解(一个有爱的江湖故事)
大佬的并查集的江湖故事,一读就懂 例子就是杭电上的畅通工程: http://acm.hdu.edu.cn/showproblem.php?pid=1232 首先在地图上给你若干个城镇,这些城镇都可以看 ...
- 并查集详解,不会的同学可以来瞅瞅,转载的,不过加了一些自己的理解。
并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...
最新文章
- Java还欠缺什么才能真正支持机器/深度学习?
- 乾颐堂安德HCIE课程3-OSPF的精华1、2类LSA,区域间的3类LSA和过滤策略
- 卷积神经网络(cnn)的体系结构
- 西门子安装未找到ssf文件_V5.3安装时显示NO SSF FILE FOUND对话框,我该怎么解决啊? 谢谢!-工业支持中心-西门子中国...
- Node.js Event loop 图解
- 数据结构时间复杂度T(n)=O(f(n))的含义
- XDeepFM高阶特征交互,特征交互:一种极深因子分解机模型
- pdg快速转换pdf源码_在手机上快速免费把图片转换成PDF文件
- Web框架-SSM框架
- linux 卸载theano,centos 安装theano
- python读写excel的图片_Python读取excel中的图片完美解决方法
- Pubwin经典问题解答100例
- 免费的java(jsp)虚拟主机jhost
- 若干思考:从如何确定投资者是风险偏好者、风险中性者还是风险规避者 到 上学真的无用吗的思考?
- 拼多多“真香”补贴之变
- java 电梯类图,电梯控制系统(用UML图理解)
- 合格站长必须知道的5点常规知识
- LR(0)项目集规范族的构造及LR(0)分析表的构造
- 工厂车间安灯(Andon)系统
- Spring - BeanFactoryPostProcessor 扩展接口