并查集
「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」
什么是并查集
举个例子简单理解,就是相同血缘的人组成了一个个家族(不考虑家庭伦理剧!)
- 两个人都没有家族但是血缘相同,那么他们俩成立一个家族
- 如果某人和某个家族的人有血缘关系,就把他加入该家族
- 如果我们发现两个不同家族的人有人血缘相同,就把两个家族合并为一个家族
最后得到的情况是,各个家族真的没有血缘关系了
总结下来并查集就是:
- 并查集可以进行集合合并的操作(并)
- 并查集可以查找元素在哪个集合中(查)
- 并查集维护的是一堆集合(集)
根据具体场景深入理解
上述例子只是有了初步的理解,具体怎么使用和如何考虑,可以在下面的例子中更有效的学习。
背景介绍
冗余连接
树可以看成是一个连通且无环的无向图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。
图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
题目分析
一共有n个点n条边,如果没有环的情况下应该仅有n-1条边才对
我们的任务是删除一条边,但不会让任何点孤立,失去联系
可以考虑按边出现次序依次选择,对每条边上的两个点,设为A,B进行分析
其情况有以下三种:
- 两个点都没有本访问过,那么我们就让A作为父亲节点,B作为子节点,AB构成了一颗树
并将点AB访问情况设置为true - 有一个节点被访问过,例如B节点被访问过,A节点没有被访问过
那么B节点可能是一个树的根,叶子节点或者中间节点,但无论哪一种只需要让A的父亲为B即可,这样A也属于B家族的一个成员了
并将B设置为ture - A,B都被访问过
(1)我们首先查找A和B节点的根节点,他们俩是不是一个家族的,如果是一个家族的说明他们俩已经被链接起来了,那么这一条边便是多余的
根据本题只会有一条多余的边,因此返回这一条即可啦
(2)如果A和B家族不一样,那么我们将A和B两大家族合并即可,我们可以把B家族的族长也就是根结点并入到A节点,或者A节点的族长之下即可
解题代码
1 | ini复制代码 public int[] findRedundantConnection(int[][] edges) { |
并查集的经典案例-克鲁斯卡尔算法
克鲁斯卡尔算法简介
克鲁斯卡尔算法是一种用来寻找最小生成树的算法(用来求加权连通图的最小生成树的算法)。在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边。
具体的操作过程为:
- 将图的所有连接线去掉,只剩顶点
- 从图的边集数组中找到权值最小的边,将边的两个顶点连接起来
- 继续寻找权值最小的边,将两个顶点之间连接起来,如果选择的边使得最小生成树出现了环路,则放弃该边,选择权值次小的边
- 直到所有的顶点都被连接在一起并且没有环路,最小生成树就生成了。
两个核心问题
问题一 对图的所有边按照权值大小进行排序。
直接采用排序算法进行排序即可,或者构建最小堆,也是不错的选择。
问题二 将边添加到最小生成树中时,怎么样判断是否形成了回路。
核心思想是记录处理,运用了并查集的处理思想
处理方式是:记录顶点在”最小生成树”中的终点,顶点的终点是”在最小生成树中与它连通的最大顶点”。然后每次需要将一条边添加到最小生存树时,判断该边的两个顶点的终点是否重合,重合的话则会构成回路。
完整版代码
1 | arduino复制代码import java.util.ArrayList; |
代码结果输出
1 | csharp复制代码加入边(6,7) 权重 1 |
本文转载自: 掘金