并查集实现思路及优化(java实现)

并查集(Union Find)

父类设计

package cn.tiger.union;

/**
 * @author FatTiger
 * @date 2020-12-08 17:16
 **/
public abstract class UnionFind {

    protected int parents[];

    public UnionFind(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity must be >= 1");
        }

        parents = new int[capacity];

        for (int i = 0; i <parents.length ; i++) {
            parents[i] = i;
        }
    }

    /**
     * 查找v所属的集合(根节点)
     * @param v
     * @return int
     * @author FatTiger
     * @date 2020/12/8 17:22
     */
    public abstract int find(int v);

    /**
     * 检查两个参数是否属于同一个集合
     * @param v1
     * @param v2
     * @return boolean
     * @author FatTiger
     * @date 2020/12/8 17:25
     */
    public boolean isSame(int v1, int v2) {
        return find(v1) == find(v2);
    }

    /**
     * 合并v1,v2两个集合
     * @param v1 
     * @param v2
     * @return void
     * @author FatTiger
     * @date 2020/12/8 17:26
     */
    public abstract void union(int v1, int v2);

    protected void rangeCheck(int v) {
        if (v < 0 || v >= parents.length) {
            throw new IllegalArgumentException("v is out of bounds");
        }
    }
}

需求分析

  • 假设有n个村庄,有些村庄之间有连接的路,有些村庄之间没有连接的路

    • 在这里插入图片描述
  • 设计一个数据结构,能够快速执行2个操作

    • 查询2个村庄之间是否有连接的路
    • 连接两个村庄
  • 数组、链表、平衡二叉树、集合?

    • 查询、连接的时间复杂度都是:O(n)
  • 并查集能够办到查询、连接的均摊时间复杂度都是O(α(n)),α(n) < 5

  • 并查集非常适合解决这类“连接”问题

并查集有两个核心操作

  • 查找(find):查找元素所在的集合(这里的集合并不是特指Set这种数据结构,是指广义的数据集合)
  • 合并(union):将两个元素所在的集合合并为一个集合

有两种常见的实现思路

  • Quick Find
    • 查找的时间复杂度:O(1)
    • 合并的时间复杂度:O(n)
  • Qucik Union
    • 查找的时间复杂度:O(logn),可以优化至O(α(n)),α(n) < 5
    • 合并的时间复杂度:O(logn),可以优化至O(α(n)),α(n) < 5

如何存储数据

  • 假设并查集处理的数据都是整形,那么可以用整形数组来存储数据

在这里插入图片描述

接口定义

在这里插入图片描述

初始化

  • 初始化时,每个元素各自属于一个单元素集合

在这里插入图片描述

QucikFind

Union

将左边集合的所有父节点直接赋值为右边的父节点

总的来说就是让v1所在集合的所有元素都指向v2的根节点

在这里插入图片描述
在这里插入图片描述

Find

直接返回v所在的父节点即可

在这里插入图片描述

代码实现

package cn.tiger.union;

/**
 * @author FatTiger
 * @date 2020-12-08 17:16
 **/
public class UnionFind {

    protected int parents[];

    public UnionFind(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity must be >= 1");
        }

        parents = new int[capacity];

        for (int i = 0; i <parents.length ; i++) {
            parents[i] = i;
        }
    }

    /**
     * 查找v所属的集合(根节点)
     * @param v
     * @return int
     * @author FatTiger
     * @date 2020/12/8 17:22
     */
    public int find(int v) {
        rangeCheck(v);

        return parents[v];
    }

    /**
     * 检查两个参数是否属于同一个集合
     * @param v1
     * @param v2
     * @return boolean
     * @author FatTiger
     * @date 2020/12/8 17:25
     */
    public boolean isSame(int v1, int v2) {
        return find(v1) == find(v2);
    }

    /**
     * 合并v1,v2两个集合
     * @param v1 
     * @param v2
     * @return void
     * @author FatTiger
     * @date 2020/12/8 17:26
     */
    public void union(int v1, int v2) {
        int p1 = find(v1);
        int p2 = find(v2);

        if (p1 == p2) return;

        for (int i = 0; i < parents.length; i++) {
            if (parents[i] == p1) {
                parents[i] = p2;
            }
        }
    }

    private void rangeCheck(int v) {
        if (v < 0 || v >= parents.length) {
            throw new IllegalArgumentException("v is out of bounds");
        }
    }
}

QuickUnion

Union

将右边的根节点作为左边的根节点,不影响原来左边的父子关系,只是多了个祖先或者兄弟

总的来说就是:让v1的根节点指向v2的根节点

在这里插入图片描述
在这里插入图片描述

Find

不断向上找父节点,找到根节点为止,返回根节点
在这里插入图片描述

代码实现

package cn.tiger.union;

/**
 * @author FatTiger
 * @date 2020-12-08 17:41
 **/
public class UnionFind_QU extends UnionFind {

    public UnionFind_QU(int capacity) {
        super(capacity);
    }

    @Override
    public int find(int v) {
        rangeCheck(v);

        while (v != parents[v]) {
            v = parents[v];
        }
        return v;
    }

    @Override
    public void union(int v1, int v2) {
        int p1 = find(v1);
        int p2 = find(v2);
        if (p1 == p2) return;

        parents[p1] = p2;
    }
}

优化

  • 在Union的过程中,可能会出现树不平衡的情况,甚至退化成链表

在这里插入图片描述

  • 有两种常见的优化方案

    • 基于size的优化:元素少的树 嫁接到 元素多的树下
    • 基于rank的优化:的树 嫁接到 的树下
基于size的优化
public class UnionFind_QU_S extends UnionFind_QU {
    private int sizes[];

    public UnionFind_QU_S(int capacity) {
        super(capacity);

        sizes = new int[capacity];

        Arrays.fill(sizes, 1);
    }


    /**
     * 合并v1,v2两个集合
     * @param v1
     * @param v2
     * @return void
     * @author FatTiger
     * @date 2020/12/8 17:26
     */
    @Override
    public void union(int v1, int v2) {
        int p1 = find(v1);
        int p2 = find(v2);

        if (p1 == p2) return;

        if (sizes[p1] < sizes[p2]) {
            parents[p1] = p2;
            sizes[p2] += sizes[p1];
        } else {
            parents[p2] = p1;
            sizes[p1] += sizes[p2];
        }

    }
}
基于rank的优化
public class UnionFind_QU_R extends UnionFind_QU {
   private int ranks[];

   public UnionFind_QU_R(int capacity) {
       super(capacity);

       ranks = new int[capacity];

       Arrays.fill(ranks, 1);
   }


   /**
    * 合并v1,v2两个集合
    * @param v1
    * @param v2
    * @return void
    * @author FatTiger
    * @date 2020/12/8 17:26
    */
   @Override
   public void union(int v1, int v2) {
       int p1 = find(v1);
       int p2 = find(v2);

       if (p1 == p2) return;

       if (ranks[p1] < ranks[p2]) {
           parents[p1] = p2;

       } else  if (ranks[p1] > ranks[p2]) {
           parents[p2] = p1;
       } else {
           parents[p1] = p2;
           ranks[p2] += 1;
       }

   }
}
路径压缩

在这里插入图片描述

public class UnionFind_QU_R_PC extends UnionFind_QU_R {


    public UnionFind_QU_R_PC(int capacity) {
        super(capacity);

    }


    @Override
    public int find(int v) {
        rangeCheck(v);

        if (parents[v] != v) {
            parents[v] = find(parents[v]);
        }
        return parents[v];
    }
}

还有两种更优的做法,不但能降低树高,实现成本也比路径压缩低

  • 路径分裂
  • 路径减半
路径分裂

使路径上的每个节点都指向其祖父节点(parent的parent)

在这里插入图片描述

public class UnionFind_QU_R_PS extends UnionFind_QU_R {


    public UnionFind_QU_R_PS(int capacity) {
        super(capacity);

    }


    @Override
    public int find(int v) {
        rangeCheck(v);

        if (parents[v] != v) {
            int p = parents[v];
            parents[v] = parents[parents[v]];

            v = p;
        }
        return v;
    }
}
路径减半

使路径上每隔一个节点就指向其祖父节点(parent的parent)

在这里插入图片描述

public class UnionFind_QU_R_PH extends UnionFind_QU_R {


    public UnionFind_QU_R_PH(int capacity) {
        super(capacity);

    }


    @Override
    public int find(int v) {
        rangeCheck(v);

        if (parents[v] != v) {
            parents[v] = parents[parents[v]];

            v = parents[v];
        }
        return v;
    }
}

总结

如果使用路径压缩、分裂或减半+基于rank或者size的优化

可以确保每个操作的均摊时间复杂度为O(α(n)),α(n) < 5

推荐搭配:

  • Quick Union
  • 基于rank的优化
  • 路径减半或路径分裂
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值