算法笔记(1)- 并查集

算法笔记(1)

1. 并查集

并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。

主要有两种操作:

  • **合并(union):**把两个不相交的集合合并为一个集合。
  • **查询(find):**查询两个元素是否在同一个集合中。

并查集的引入:

并查集的重要思想在于,用集合中的一个元素代表集合。有一个有趣的比喻,把集合比喻成帮派,而代表元素则是帮主。接下来用这个比喻,看看并查集是如何运作的。

算法笔记(1)- 并查集_第1张图片

最开始,所有大侠各自为战。他们各自的帮助自然就是自己。(对于只有一个元素的集合,代表元素自然是唯一的那个元素)。

现在1号和3号比武,假设1号赢了(这里具体是谁赢的不重要),那么3号就认1号做帮主(合并1号和3号所在的集合,1号为代表元素)

算法笔记(1)- 并查集_第2张图片

现在2号想和3号比武(合并3号和2号所在的集合),但3号表示,别跟我打,让我帮主来收拾你(合并代表元素)。不妨设这次又是1号赢了,那么2号也认1号做帮主。

算法笔记(1)- 并查集_第3张图片
现在我们假设4,5,6号也进行了一番帮派合并,江湖局势变成下面这样:

算法笔记(1)- 并查集_第4张图片

现在假设2号想和6号比,和刚才说的一样,喊帮主1号和4号出来打一架。1号战胜后,4号认1号为帮主,当然他的手下也都是跟着投降了。

算法笔记(1)- 并查集_第5张图片
好了,比喻结束了。如果你有一点图论基础,相信你已经觉察到,这是一个状的结构,要寻找集合的代表元素,只需要一层一层往上访问父节点(图中箭头所指的圆),直达树的根节点(图中橙色的圆)即可。根节点的父节点是它自己。我们可以直接把它画成一棵树:

算法笔记(1)- 并查集_第6张图片
用这种方法,我们可以写出最简单的并查集代码。

初始化

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. 路径压缩

最简单的并查集效率是比较低的。例如,看下面的这个场景:

算法笔记(1)- 并查集_第7张图片
现在我们想要union(2,3),于是从2找到1,fa[1]=3,于是变成了这样:

算法笔记(1)- 并查集_第8张图片
然后我们又找来了一个元素4,并需要执行union(2,4):

算法笔记(1)- 并查集_第9张图片
从2找到1,再找到3,然后fa[3]=4,于是变成了这样:

算法笔记(1)- 并查集_第10张图片
这样可能会形成一条长长的链,随着链越来越长,我们想要从底部找到根节点会变得越来越难。

我们就可以使用路径压缩的方法。既然我们只关心一个元素对应的根节点,那我们希望每个元素到根节点的路径尽可能的短,最好只需要一步,像这样:

算法笔记(1)- 并查集_第11张图片

只要我们在查询的过程中,把沿途的每个节点的父节点都设为根节点即可。

合并(路径压缩)

public int find(int x){
    if(x==fa[x])
    	return x;
    else{
        fa[x]=find(fa[x]);//父节点设为根节点
        return fa[x];//返回父节点
    }   
}

你可能感兴趣的:(算法)