傻子都能看懂的并查集算法

一、定义

其实并查集顾名思义就是有“合并集合(Union)”和“查找两个元素是否在同一集合(isSameSet)”两种操作的关于数据结构的一种算法。举个例子。如下图

有a、b、c、d、e五个元素,每个元素处于自己的集合中,比如a元素所在的集合只有a这么一个元素,其他也类似。

然后我们定义每个元素的父亲指针指向自己,如下图所示。

二、并查集的合并操作(Union方法)

例如,我们想要将a所在的集合和b所在的集合合并起来。我们首先通过a的父亲指针一直往上找,找到a的最顶部的根节点,即为a然后同样通过b的父亲指针一直往上找,找到b的最顶部的根节点,即为b最后将集合元素较少的根节点挂到集合元素较大的根节点,该例子中是b和a两个集合的元素都为1,谁挂谁都可以,假如是b挂a,结果如下图。

此时,如果再执行Union(b,c)这一操作,即合并b所在的集合和c所在的集合。同样,通过b的父亲指针一直往上找,找到b的最顶部的根节点,即为a,然后同样通过c的父亲指针一直往上找,找到c的最顶部的根节点,即为c。然后把c挂到a上面。如下图。

此时,可以看到,a、b、c三个元素属于同一集合了,而d和e还是属于各自的两个不同的集合。

 

三、并查集的查找两个元素是否在同一集合操作(isSameSet方法)

现在我们想要执行isSameSet(b,c)方法,即判断b和c是否属于同一集合,同样的,我们可以通过b的父亲指针一直往上找,找到b的最顶部的根节点,为a。然后通过c的父亲指针一直往上找,找到c的最顶部的根节点,也为a。此时我们就可以知道,两者的根节点相等,即b和c属于同一集合。

 

四、如何加速整个算法?

在上面的两个操作中,我们都涉及了同样的一个操作,即找到某个元素的根节点元素,我们暂且定义该操作为findHead操作,那么一直往上找的这个遍历过程我们可以做如下优化。比如,并查集的某一状态如下图所示。

当我们执行Union或者isSameSet操作时候,都会涉及这个findHead操作,比如,执行isSameSet(e,c)的时候,即判断e和c是否属于同一集合,那么我们首先要找到e的根节点,在往上找的过程中,我们可以记录找寻的路径,即会经过e、c、b、a,然后把这些经过的元素的父亲指针直接指向根节点a。(事实上,只需要改变e、c节点的父亲指针指向即可)。那么下次在找c的根节点的时候,就无需再遍历了。

通过该优化后,可以使得并查集的Union和isSameSet两个操作的时间复杂度均为O(1).

 

五、参考代码

class Element {   //1---->元素1.仅仅是包一层的意思。    public int value;    public Element(int value) {      this.value = value;    }   }  public static class UnionFindSet{    public HashMap<Integer,Element> elementMap;    // key  某个元素  value 该元素的父    public HashMap<Element, Element> fatherMap;    // key 某个集合的代表元素   value 该集合的大小    public HashMap<Element, Integer> sizeMap;        public UnionFindSet(ArrayList<Integer> arrList) {      elementMap = new HashMap<>();      fatherMap = new HashMap<>();      sizeMap = new HashMap<>();            for(Integer item:arrList) {        Element ele = new Element(item);        elementMap.put(item, ele);        fatherMap.put(ele, ele);        sizeMap.put(ele, 1);      }    }    public Element findHead(Element ele) {      Stack<Element> stack = new Stack();            while(ele != fatherMap.get(ele)) {        stack.add(ele);        ele = fatherMap.get(ele);      }            while(!stack.isEmpty()) {        fatherMap.put(stack.pop(), ele);      }            return ele;    }        public boolean isSameSet(Integer a, Integer b) {      if (elementMap.containsKey(a) && elementMap.containsKey(b)) {        return findHead(elementMap.get(a)) == findHead(elementMap.get(b));      }      return false;    }    public void union(Integer a, Integer b) {      if (elementMap.containsKey(a) && elementMap.containsKey(b)) {        Element aF = findHead(elementMap.get(a));        Element bF = findHead(elementMap.get(b));        if (aF != bF) {          Element big = sizeMap.get(aF) >= sizeMap.get(bF) ? aF : bF;          Element small = big == aF ? bF : aF;          fatherMap.put(small, big);          sizeMap.put(big, sizeMap.get(aF) + sizeMap.get(bF));          sizeMap.remove(small);        }      }    }   }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值