数据结构——并查集

并查集是一种数据结构,是树的一种应用,用于处理一些不交集(一系列没有重复元素的集合)的合并以及查询问题。并查集支持如下操作:

  • 查询:查询某个元素属于哪个集合,通常是返回集合内的一个“代表元素”。这个操作一般是为了判断两个元素是否在同一个集合之中。
  • 合并:将两个集合合并为一个。
  • 添加:添加一个新集合,其中有一个新元素。不如查询和合并操作重要。

我们来看下面一个比较有趣的并查集应用的例子:

话说江湖上散落着各式各样的大侠,有上千个之多。他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架。但大侠们有一个优点就是讲义气,绝对不打自己的朋友。而且他们信奉“朋友的朋友就是我的朋友”,只要是能通过朋友关系串联起来的,不管拐了多少个弯,都认为是自己人。这样一来,江湖上就形成了一个一个的群落,通过两两之间的朋友关系串联起来。而不在同一个群落的人,无论如何都无法通过朋友关系连起来,于是就可以放心往死了打。但是两个原本互不相识的人,如何判断是否属于一个朋友圈呢?

我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物,这样,每个圈子就可以这样命名“齐达内朋友之队”“罗纳尔多朋友之队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。如此一来,门派便产生了。

在上面的例子中,我们可以认为每个门派都是一个集合,门派的掌门人作为集合的“代表元素”,通过确认双方的掌门人是否是同一个人来确定对方是否是自己人。对于没有门派的大侠,我们也可以认为他自己组成一个门派,掌门人就是他自己。集合的合并就类似于门派的合并,门派合并后需要重新推出一个掌门人,也就是要重新选取集合的“代表元素”。

实现方法一

实现方法一总是记住一个集合中编号最小的元素,类似一个门派中的大侠总是记住自己的掌门人是谁。

  • 我们定义一个数组set[1…n],其中set[i]表示元素i所在集合;
  • 用编号最小的元素标记所在集合。

我们来看下面一个例子,假设有以下不相交集合:

{1, 3, 7}, {4}, {2, 5, 9 10}, {6, 8}

数组set[1…n]为:

i 1 2 3 4 5 6 7 8 9 10
set[i] 1 2 1 4 2 6 1 6 2 2

其中1, 3, 7组成一个集合,他们中间最小为1,因此set[1]、set[3]、set[7]都为1。

假设我们现在要将{1, 3, 7}、{2, 5, 9 10}这两个集合合并,合并方式如下:
方法一合并过程

数组set[1…n]更新为:

i 1 2 3 4 5 6 7 8 9 10
set[i] 1 1 1 4 1 6 1 6 1 1

代码实现

初始化

初始时我们传入元素个数,并将set[i]设置为自身。

    private int[] set;

    /**
     * 构造方法,初始化set数组
     *
     * @param num 元素数量
     */
    public MergeFindSet1(int num) {
   
        set = new int[num];
        for (int i = 0; i < num; i++) {
   
            // 初始时初始化为自身
            set[i] = i;
        }
    }
查找方法

查找方法比较简单,由于set中记录的永远是集合当中的代表元素,因此只需要返回set[i]即可:

    /**
     * 查询某个元素属于哪个集合
     *
     * @param num
     * @return
     */
    public int find(int num) {
   
        return set[num];
    }
合并方法

在并查集的合并方法中,我们传入两个元素的编号,代表这两个元素在同一个集合当中。我们先找到两个元素所在集合的代表元素,接着找两个代表元素中编号大的那个,将编号大的元素的set[max]赋值为编号小代表元素min。同时由于方法一总是记住的是集合当中编号最小的,因此还需要更新set[i]=max的所有元素。

    /**
     * 集合的合并操作
     *
     * @param a
     * @param b
     */
    public void merge(int a, int b) {
   
        int findA = find(a);
        int findB = find(b);
        int max = Math.max(findA, findB);
        int min = Math.min(findA, findB);
        // 用编号小的元素标记所在集合
        // 同时还需要把以前同一个集合中的元素的set[i]更新
        for (int i = 0; i < set.length; i++) {
   
            if (set[i] == max) {
   
                set[i] = min;
            }
        }
    }
测试
    public static void main(String[] args){
   
        MergeFindSet1 mergeFindSet1 = new MergeFindSet1(11);
        mergeFindSet1.merge(1, 3);
        mergeFindSet1.merge(3, 7);
        mergeFindSet1.merge(2, 5);
        mergeFindSet1.merge(5, 9);
        mergeFindSet1.merge(9, 10);
        mergeFindSet1.merge(6, 8);

        for (int i = 0; i < 11
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值