并查集java实现

并查集定义

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。常常在使用中以森林来表示。

借鉴一个看到的故事

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

我们可以在每个朋友圈内推举出一个比较有名望的人,作为该圈子的代表人物。这样,每个圈子就可以这样命名“中国同胞队”美国同胞队”……两人只要互相对一下自己的队长是不是同一个人,就可以确定敌友关系了。

但是还有问题啊,大侠们只知道自己直接的朋友是谁,很多人压根就不认识队长要判断自己的队长是谁,只能漫无目的的通过朋友的朋友关系问下去:“你是不是队长?你是不是队长?”这样,想打一架得先问个几十年,饿都饿死了,受不了。这样一来,队长面子上也挂不住了,不仅效率太低,还有可能陷入无限循环中。

于是队长下令,重新组队。队内所有人实行分等级制度,形成树状结构,我队长就是根节点,下面分别是二级队员、三级队员。每个人只要记住自己的上级是谁就行了。遇到判断敌友的时候,只要一层层向上问,直到最高层,就可以在短时间内确定队长是谁了。由于我们关心的只是两个人之间是否是一个帮派的,至于他们是如何通过朋友关系相关联的,以及每个圈子内部的结构是怎样的,甚至队长是谁,都不重要了。所以我们可以放任队长随意重新组队,只要不搞错敌友关系就好了。于是,门派产生了。

代码实现:

在代码中,通常使用一个 parent数组表示所有元素,数组下标表示具体每一个元素,每个数组值表示该元素对应的父节点(也就是它的上级),如果该元素是根节点或者是单独的节点,该元素的值为自己本身,如 parent[3] = 3

例子:

 

 


public class QuickFindUF {
    private int[] parent = new int[5]; //定义长度为5的数组,表示每个元素

    public void init() {  //初始化数组,开始时,每个元素指向自己
        for(int i = 0; i < parent.length; i++) {
            parent[i] = i;
        }
    }

    private int root(int i) {  //找到节点i的根节点
        while(i != parent[i]) {
            i = parent[i];
        }
        return i;
    }

    public void union(int p, int q) {  //快速合并p q两个节点:先找到两个根节点,然后将其中一个根节点指向另一个根节点
        int proot = root(p);
        int qroot = root(q);
        parent[proot] = qroot;
    }

   public boolean connected(int p, int q) {  //判断节点p和q是否相连
        return root(p) == root(q);
    }


    public static void main(String[] args) {
        init();
        union(0, 2);
        union(1, 4);
    }
 



}

典型应用:

并查集常常用来判断在一个图中是否存在回路(是否可以生成树),以及用来判断图的联通性问题

 

leetcode 684. 冗余连接

在本问题中, 树指的是一个连通且无环的无向图。

输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以组成的二维数组。每一个的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v无向图的边。

返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v

示例 1:

输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
  1
 / \
2 - 3

示例 2:

输入: [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
解释: 给定的无向图为:
5 - 1 - 2
    |   |
    4 - 3

注意:

  • 输入的二维数组大小在 3 到 1000。
//答案
class Solution {
    public int[] parent;
    public int[] findRedundantConnection(int[][] edges) {
        parent = new int[edges.length + 1];
        init(edges.length);
        for(int i = 0; i < edges.length; i++) {
            if(connected(edges[i][0], edges[i][1])) {
                return edges[i];
            } else {
                union(edges[i][0], edges[i][1]);
            }
        }
        return edges[edges.length];

    }

    private int root(int i) {
        while(i != parent[i]) {
            i = parent[i];
        }
        return i;
    }
    public boolean connected(int p, int q) {
        return root(p) == root(q);
    }
    public void union(int p, int q) {
        int proot = root(p);
        int qroot = root(q);
        parent[proot] = qroot;
    }
    public void init(int N) {
        for(int i = 1; i <= N; i++) {
            parent[i] = i;
        }
    }


}

 

leetcode 721. 账户合并

给定一个列表 accounts,每个元素 accounts[i] 是一个字符串列表,其中第一个元素 accounts[i][0] 是 名称 (name),其余元素是 emails 表示该账户的邮箱地址。

现在,我们想合并这些账户。如果两个账户都有一些共同的邮箱地址,则两个账户必定属于同一个人。请注意,即使两个账户具有相同的名称,它们也可能属于不同的人,因为人们可能具有相同的名称。一个人最初可以拥有任意数量的账户,但其所有账户都具有相同的名称。

合并账户后,按以下格式返回账户:每个账户的第一个元素是名称,其余元素是按字符 ASCII 顺序排列的邮箱地址。账户本身可以以任意顺序返回。

示例 1:

输入:
accounts = [["John", "johnsmith@mail.com", "john00@mail.com"], ["John", "johnnybravo@mail.com"], ["John", "johnsmith@mail.com", "john_newyork@mail.com"], ["Mary", "mary@mail.com"]]
输出:
[["John", 'john00@mail.com', 'john_newyork@mail.com', 'johnsmith@mail.com'],  ["John", "johnnybravo@mail.com"], ["Mary", "mary@mail.com"]]
解释:
第一个和第三个 John 是同一个人,因为他们有共同的邮箱地址 "johnsmith@mail.com"。 
第二个 John 和 Mary 是不同的人,因为他们的邮箱地址没有被其他帐户使用。
可以以任何顺序返回这些列表,例如答案 [['Mary','mary@mail.com'],['John','johnnybravo@mail.com'],
['John','john00@mail.com','john_newyork@mail.com','johnsmith@mail.com']] 也是正确的。
//答案
class Solution {
    public List<List<String>> accountsMerge(List<List<String>> accounts) {
        Map<String, Integer> email2index = new HashMap<String, Integer>();
        Map<String, String> email2name = new HashMap<String, String>();

        int count = 0;
        for(List<String> account : accounts) {
            String name = account.get(0);
            int size = account.size();
            for(int i = 1; i < size; i++) {
                String email = account.get(i);
                if(!email2index.containsKey(email)) {
                    email2index.put(email, count++);
                    email2name.put(email, name);
                }
            }
        }

        FindUF df = new FindUF();
        df.init(count);

        for(List<String> account : accounts) {
            String firstEmail = account.get(1);
            int firstIndex = email2index.get(firstEmail);
            int size = account.size();
            for(int i = 2; i < size; i++) {
                String nextEmail = account.get(i);
                int nextIndex = email2index.get(nextEmail);
                df.union(firstIndex, nextIndex);
            }
        }

        Map<Integer, List<String>> index2emails = new HashMap<Integer, List<String>>();
        for(String email : email2index.keySet()) {
            int index = df.root(email2index.get(email));
            List<String> account = index2emails.getOrDefault(index, new ArrayList<String>());
            account.add(email);
            index2emails.put(index, account);
        }

        List<List<String>> merged = new ArrayList<List<String>>();
        for(List<String> emails : index2emails.values()) {
            Collections.sort(emails);
            String name = email2name.get(emails.get(0));
            List<String> account = new ArrayList<String>();
            account.add(name);
            account.addAll(emails);
            merged.add(account);
        }
       
       return merged;
    }


}

class FindUF {
    int[] parent;
    public void init(int n) {
        parent = new int[n];
        for(int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    public int root(int i) {
        while(parent[i] != i) {
            i = parent[i];
        }
        return i;
    }

    public void union(int p, int q) {
        int p1 = root(p);
        int q1 = root(q);
        parent[q1] = p1;
    }

      public boolean isConnected(int p, int q) {
        return root(q) == root(p);
    }
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值