算法笔记(1)- 并查集
算法笔记(1)
1. 并查集
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
主要有两种操作:
- **合并(union):**把两个不相交的集合合并为一个集合。
- **查询(find):**查询两个元素是否在同一个集合中。
并查集的引入:
并查集的重要思想在于,用集合中的一个元素代表集合。有一个有趣的比喻,把集合比喻成帮派,而代表元素则是帮主。接下来用这个比喻,看看并查集是如何运作的。
最开始,所有大侠各自为战。他们各自的帮助自然就是自己。(对于只有一个元素的集合,代表元素自然是唯一的那个元素)。
现在1号和3号比武,假设1号赢了(这里具体是谁赢的不重要),那么3号就认1号做帮主(合并1号和3号所在的集合,1号为代表元素)
现在2号想和3号比武(合并3号和2号所在的集合),但3号表示,别跟我打,让我帮主来收拾你(合并代表元素)。不妨设这次又是1号赢了,那么2号也认1号做帮主。
现在我们假设4,5,6号也进行了一番帮派合并,江湖局势变成下面这样:
现在假设2号想和6号比,和刚才说的一样,喊帮主1号和4号出来打一架。1号战胜后,4号认1号为帮主,当然他的手下也都是跟着投降了。
好了,比喻结束了。如果你有一点图论基础,相信你已经觉察到,这是一个树状的结构,要寻找集合的代表元素,只需要一层一层往*问父节点(图中箭头所指的圆),直达树的根节点(图中橙色的圆)即可。根节点的父节点是它自己。我们可以直接把它画成一棵树:
用这种方法,我们可以写出最简单的并查集代码。
初始化
public void init(int n){
for(int i = 0;i<=n;i++){
fa[i] = i;
}
}
假如有编号1,2,3,…,n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这样是可行的)。一开始,我们先将它们的父节点设为自己。
查询
public int find(int index) {
while(fa[index] != index){
index = fa[index];
}
return index;
}
要判断两个元素是否属于一个集合,只需要看它们的根节点是否相同即可。
合并
public void union(int i,int j){
fa[find(i)]=find[j];
}
合并操作也很简单,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者。
2. 路径压缩
最简单的并查集效率是比较低的。例如,看下面的这个场景:
现在我们想要union(2,3),于是从2找到1,fa[1]=3,于是变成了这样:
然后我们又找来了一个元素4,并需要执行union(2,4):
从2找到1,再找到3,然后fa[3]=4,于是变成了这样:
这样可能会形成一条长长的链,随着链越来越长,我们想要从底部找到根节点会变得越来越难。
我们就可以使用路径压缩的方法。既然我们只关心一个元素对应的根节点,那我们希望每个元素到根节点的路径尽可能的短,最好只需要一步,像这样:
只要我们在查询的过程中,把沿途的每个节点的父节点都设为根节点即可。
合并(路径压缩)
public int find(int x){
if(x==fa[x])
return x;
else{
fa[x]=find(fa[x]);//父节点设为根节点
return fa[x];//返回父节点
}
}
上一篇: Java学习之异常