目录
前言:
原理:
并查集API设计
1.UF(int N)构造方法实现
2.union(int p,int q)合并方法实现
代码实现:
UF_Tree算法优化
1 UF_Tree API设计
路径压缩
案例-畅通工程
前言:
上一章我给大家介绍了红黑树这种树结构,以及实现原理,在这一章我给大家介绍简单的并查集,它也是一种树结构,但相对于二叉查找树,红黑树等,会相对比较简单,好理解。
原理:
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
并查集结构 并查集也是一种树型结构,但这棵树跟我们之前讲的二叉树、红黑树、 B 树等都不一样,这种树的要求比较简单: 1. 每个元素都唯一的对应一个结点; 2. 每一组数据中的多个元素都在同一颗树中; 3. 一个组中的数据对应的树和另外一个组中的数据对应的树之间没有任何联系; 4. 元素在树中并没有子父级关系的硬性要求;
并查集API设计
1.UF(int N)构造方法实现
1.
初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为
N
个组;
2.
初始化数组
eleAndGroup
;
3.
把
eleAndGroup
数组的索引看做是每个结点存储的元素,把
eleAndGroup
数组每个索引处的值看做是该结点 所在的分组,那么初始化情况下,i索引处存储的值就是
i
2.union(int p,int q)合并方法实现
2.union(int p,int q)合并方法实现
1. 如果p和q已经在同一个分组中,则无需合并
2. 如果 p 和 q 不在同一个分组,则只需要将 p 元素所在组的所有的元素的组标识符修改为 q 元素所在组的标识符即 可 3. 分组数量 -1
代码实现:
public class UF {
//记录结点元素和该元素所在分组的标识
private int[] eleAndGroup;
//记录分组的组数
private int count;
// 构造方法
public UF(int N) {
//初始情况下,每个元素都在一个独立的分组中,所以,初始情况下,并查集中的数据默认分为N个组
this.count=N;
//初始化数组
this.eleAndGroup=new int[N];
//把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该结点所在的分组,那么初始化情况下,i索引处存储的值就是i
for(int i=0;i
大家大脑想想要想把一个N个小组,全部联合在一起那我们至少要多少次使用union方法?想必大家都知道是N-1次,而我们在union方法中又使用for循环,也就是说将所有的分组联合到一起,合并算法的时间复杂度为O(n^2)。如果是这样那么可能对于很少的分组情况可能会相对而言要好一点,那么对于像计算网络问题互联可能拥有上千万而言,那就是不乐观的。所以我们需要对算法进行优 化。
UF_Tree算法优化
为了提升
union
算法的性能,我们需要重新设计
fifind
方法和
union
方法的实现,此时我们先需要对我们的之前数据结构中的eleAndGourp
数组的含义进行重新设定:
1.
我们仍然让
eleAndGroup
数组的索引作为某个结点的元素;
2.eleAndGroup[i]
的值不再是当前结点所在的分组标识,而是该结点的父结点;
1 UF_Tree API设计
1. fifind(int p)
查询方法实现
1.
判断当前元素
p
的父结点
eleAndGroup[p]
是不是自己,如果是自己则证明已经是根结点了;
2.
如果当前元素
p
的父结点不是自己,则让
p=eleAndGroup[p]
,继续找父结点的父结点
,
直到找到根结点为止;
2.union(int p,int q)
合并方法实现
1.
找到
p
元素所在树的根结点
2.
找到
q
元素所在树的根结点
3.
如果
p
和
q
已经在同一个树中,则无需合并;
4.
如果
p
和
q
不在同一个分组,则只需要将
p
元素所在树根结点的父结点设置为
q
元素的根结点即可;
5.
分组数量
-1
public class UF_Tree {
private int[] eleAndGroup;
private int count;
public UF_Tree(int N) {
this.count=N;
this.eleAndGroup=new int[N];
for(int i=0;i
优化后的性能分析
我们优化后的算法
union
,如果要把并查集中所有的数据连通,仍然至少要调用
N-1
次
union
方法,但是,我们发现 union方法中已经没有了
for
循环,所以
union
算法的时间复杂度由
O(N^2)
变为了
O(N)
。
但是这个算法仍然有问题,因为我们之前不仅修改了
union
算法,还修改了
find
算法。我们修改前的
fifind
算法的时 间复杂度在任何情况下都为O(1)
,但修改后的
fifind
算法在最坏情况下是
O(N)
:
在union方法中调用了fifind方法,所以在最坏情况下union算法的时间复杂度仍然为O(N^2)。
路径压缩
UF_Tree
中最坏情况下
union
算法的时间复杂度为
O(N^2)
,其最主要的问题在于最坏情况下,树的深度和数组的大 小一样,如果我们能够通过一些算法让合并时,生成的树的深度尽可能的小,就可以优化fifind
方法。
之前我们在
union
算法中,合并树的时候将任意的一棵树连接到了另外一棵树,这种合并方法是比较暴力的,如果 我们把并查集中每一棵树的大小记录下来,然后在每次合并树的时候,把较小的树连接到较大的树上,就可以减小 树的深度
只要我们保证每次合并,都能把小树合并到大树上,就能够压缩合并后新树的路径,这样就能提高
fifind
方法的效 率。为了完成这个需求,我们需要另外一个数组来记录存储每个根结点对应的树中元素的个数,并且需要一些代码 调整数组中的值。
public class UF_Tree_Weighted {
//记录结点元素和该元素所的父结点
private int[] eleAndGroup;
//记录并查集中数据的分组个数
private int count;
//存储每个根结点对应的树中元素的个数
private int[] sz;
public UF_Tree_Weighted(int N) {
this.count=N;
this.eleAndGroup=new int[N];
// /把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该 结点的父结点,那么初始化情况下,i索引处存储的值就是i
for(int i=0;i
案例-畅通工程
题目概述:
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府
“
畅通工程
”
的目 标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问 最少还需要建设多少条道路?
在我们的测试数据文件夹中有一个
trffiffiffic_project.txt
文件,它就是诚征道路统计表,下面是对数据的解释:
总共有20个城市,目前已经修改好了7条道路,问还需要修建多少条道路,才能让这20个城市之间全部相通?
解题思路:
1.
创建一个并查集
UF_Tree_Weighted(20);
2.
分别调用
union(0,1),union(6,9),union(3,8),union(5,11),union(2,12),union(6,10),union(4,8)
,表示已经修建好的 道路把对应的城市连接起来;
3.
如果城市全部连接起来,那么并查集中剩余的分组数目为
1
,所有的城市都在一个树中,所以,只需要获取当前 并查集中剩余的数目,减去1
,就是还需要修建的道路数目;
代码实现
public class Traffic_Project_Test {
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(Traffic_Project.class.getClassLoader().getResourceAsStream("traffic_projec t.txt")));
读取城市数目,初始化并查集
int totalNumber=Integer.parseInt(br.readLine());
UF_Tree_Weighted uf=new UF_Tree_Weighted(totalNumber);
//读取已经修建好的道路数目
int roadNumber=Integer.parseInt(br.readLine());
//循环读取已经修建好的道路,并调用union方法
for(int i=0;i
并查集的讲解到这也就结束了,那我们在下期再见!!!
活动地址:CSDN21天学习挑战赛
大家大脑想想要想把一个N个小组,全部联合在一起那我们至少要多少次使用union方法?想必大家都知道是N-1次,而我们在union方法中又使用for循环,也就是说将所有的分组联合到一起,合并算法的时间复杂度为O(n^2)。如果是这样那么可能对于很少的分组情况可能会相对而言要好一点,那么对于像计算网络问题互联可能拥有上千万而言,那就是不乐观的。所以我们需要对算法进行优 化。
UF_Tree算法优化
为了提升
union
算法的性能,我们需要重新设计
fifind
方法和
union
方法的实现,此时我们先需要对我们的之前数据结构中的eleAndGourp
数组的含义进行重新设定:
1.
我们仍然让
eleAndGroup
数组的索引作为某个结点的元素;
2.eleAndGroup[i]
的值不再是当前结点所在的分组标识,而是该结点的父结点;
1 UF_Tree API设计
1. fifind(int p)
查询方法实现
1.
判断当前元素
p
的父结点
eleAndGroup[p]
是不是自己,如果是自己则证明已经是根结点了;
2.
如果当前元素
p
的父结点不是自己,则让
p=eleAndGroup[p]
,继续找父结点的父结点
,
直到找到根结点为止;
2.union(int p,int q)
合并方法实现
1.
找到
p
元素所在树的根结点
2.
找到
q
元素所在树的根结点
3.
如果
p
和
q
已经在同一个树中,则无需合并;
4.
如果
p
和
q
不在同一个分组,则只需要将
p
元素所在树根结点的父结点设置为
q
元素的根结点即可;
5.
分组数量
-1
public class UF_Tree {
private int[] eleAndGroup;
private int count;
public UF_Tree(int N) {
this.count=N;
this.eleAndGroup=new int[N];
for(int i=0;i
优化后的性能分析
我们优化后的算法
union
,如果要把并查集中所有的数据连通,仍然至少要调用
N-1
次
union
方法,但是,我们发现 union方法中已经没有了
for
循环,所以
union
算法的时间复杂度由
O(N^2)
变为了
O(N)
。
但是这个算法仍然有问题,因为我们之前不仅修改了
union
算法,还修改了
find
算法。我们修改前的
fifind
算法的时 间复杂度在任何情况下都为O(1)
,但修改后的
fifind
算法在最坏情况下是
O(N)
:
在union方法中调用了fifind方法,所以在最坏情况下union算法的时间复杂度仍然为O(N^2)。
路径压缩
UF_Tree
中最坏情况下
union
算法的时间复杂度为
O(N^2)
,其最主要的问题在于最坏情况下,树的深度和数组的大 小一样,如果我们能够通过一些算法让合并时,生成的树的深度尽可能的小,就可以优化fifind
方法。
之前我们在
union
算法中,合并树的时候将任意的一棵树连接到了另外一棵树,这种合并方法是比较暴力的,如果 我们把并查集中每一棵树的大小记录下来,然后在每次合并树的时候,把较小的树连接到较大的树上,就可以减小 树的深度
只要我们保证每次合并,都能把小树合并到大树上,就能够压缩合并后新树的路径,这样就能提高
fifind
方法的效 率。为了完成这个需求,我们需要另外一个数组来记录存储每个根结点对应的树中元素的个数,并且需要一些代码 调整数组中的值。
public class UF_Tree_Weighted {
//记录结点元素和该元素所的父结点
private int[] eleAndGroup;
//记录并查集中数据的分组个数
private int count;
//存储每个根结点对应的树中元素的个数
private int[] sz;
public UF_Tree_Weighted(int N) {
this.count=N;
this.eleAndGroup=new int[N];
// /把eleAndGroup数组的索引看做是每个结点存储的元素,把eleAndGroup数组每个索引处的值看做是该 结点的父结点,那么初始化情况下,i索引处存储的值就是i
for(int i=0;i
案例-畅通工程
题目概述:
某省调查城镇交通状况,得到现有城镇道路统计表,表中列出了每条道路直接连通的城镇。省政府
“
畅通工程
”
的目 标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问 最少还需要建设多少条道路?
在我们的测试数据文件夹中有一个
trffiffiffic_project.txt
文件,它就是诚征道路统计表,下面是对数据的解释:
总共有20个城市,目前已经修改好了7条道路,问还需要修建多少条道路,才能让这20个城市之间全部相通?
解题思路:
1.
创建一个并查集
UF_Tree_Weighted(20);
2.
分别调用
union(0,1),union(6,9),union(3,8),union(5,11),union(2,12),union(6,10),union(4,8)
,表示已经修建好的 道路把对应的城市连接起来;
3.
如果城市全部连接起来,那么并查集中剩余的分组数目为
1
,所有的城市都在一个树中,所以,只需要获取当前 并查集中剩余的数目,减去1
,就是还需要修建的道路数目;
代码实现
public class Traffic_Project_Test {
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(Traffic_Project.class.getClassLoader().getResourceAsStream("traffic_projec t.txt")));
读取城市数目,初始化并查集
int totalNumber=Integer.parseInt(br.readLine());
UF_Tree_Weighted uf=new UF_Tree_Weighted(totalNumber);
//读取已经修建好的道路数目
int roadNumber=Integer.parseInt(br.readLine());
//循环读取已经修建好的道路,并调用union方法
for(int i=0;i
并查集的讲解到这也就结束了,那我们在下期再见!!!
活动地址:CSDN21天学习挑战赛



