今天学习一种新的数据结构并查集。“并”表示合并,“查”表示查找,“集”表示集合。其基本思想是用 father[i] 表示元素 i 的父节点。例如 father[1] = 2 表示元素 1 的父节点是 2。如果 father[i] = i,那么说明 i 是根节点,根节点作为一个集合的标识,如下图表示两个集合,它们的根节点分别是 1 和 5。当然,如果不使用数组来记录,而使用 map 来记录,那么可以使用 father.get(i) = null 来表示根节点。

1.并查集的基本操作

下面定义了并查集的类,并实现了基本操作。

class UnionFind {// 用 Map 在存储并查集,表达的含义是 key 的父节点是 valueprivate Map<Integer,Integer> father;// 0.构造函数初始化public UnionFind(int n) {father = new HashMap<Integer,Integer>();for (int i = 0; i < n; i++) {father.put(i, null);}}// 1.添加:初始加入时,每个元素都是一个独立的集合,因此public void add(int x) { // 根节点的父节点为nullif (!father.containsKey(x)) {father.put(x, null);}}// 2.查找:反复查找父亲节点。public int findFather(int x) {int root = x;  // 寻找x祖先节点保存到root中while(father.get(root) != null){root = father.get(root);}while(x != root){ // 路径压缩,把x到root上所有节点都挂到root下面int original_father = father.get(x);   // 保存原来的父节点father.put(x,root);      // 当前节点挂到根节点下面x = original_father; // x赋值为原来的父节点继续执行刚刚的操作}return root;}// 3.合并:把两个集合合并为一个,只需要把其中一个集合的根节点挂到另一个集合的根节点下方public void union(int x, int y) {   // x的集合和y的集合合并int rootX = find(x);int rootY = find(y);if (rootX != rootY){   // 节点联通只需要一个共同祖先,无所谓谁是根节点father.put(rootX,rootY);}}// 4.判断:判断两个元素是否同属一个集合public boolean isConnected(int x, int y) {return find(x) == find(y);}
}
2.练习:力扣721. 账户合并[1]

给定一个列表 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']] 也是正确的。

示例 2:

输入:accounts = [["Gabe","Gabe0@m.co","Gabe3@m.co","Gabe1@m.co"],["Kevin","Kevin3@m.co","Kevin5@m.co","Kevin0@m.co"],["Ethan","Ethan5@m.co","Ethan4@m.co","Ethan0@m.co"],["Hanzo","Hanzo3@m.co","Hanzo1@m.co","Hanzo0@m.co"],["Fern","Fern5@m.co","Fern1@m.co","Fern0@m.co"]]
输出:[["Ethan","Ethan0@m.co","Ethan4@m.co","Ethan5@m.co"],["Gabe","Gabe0@m.co","Gabe1@m.co","Gabe3@m.co"],["Hanzo","Hanzo0@m.co","Hanzo1@m.co","Hanzo3@m.co"],["Kevin","Kevin0@m.co","Kevin3@m.co","Kevin5@m.co"],["Fern","Fern0@m.co","Fern1@m.co","Fern5@m.co"]]

解答如下:

class Solution {public List<List<String>> accountsMerge(List<List<String>> accounts) {Map<String, Integer> emailToId = new HashMap<>();   // 记录每个邮箱对应的idint n = accounts.size();UnionFind myUnion = new UnionFind(n);    // 定义并查集,并查集记录的都是id,用id作为集合的标识而不是邮箱// 1.遍历所有 email,同一个账户合并为同一个集合for (int i = 0; i < n; i++) {List<String> mails = accounts.get(i);int nums = mails.size();for (int j = 1; j < nums; j++) {String curEmail = mails.get(j);// 邮箱的去重体现在这里,没出现过才记录,出现过就不记录了,只合并集合if (!emailToId.containsKey(curEmail)) { // 如果还没出现过该邮箱emailToId.put(curEmail, i);         // 就把该邮箱所属的id记录下来} else {                                // 如果出现过该邮箱myUnion.union(i, emailToId.get(curEmail));  // 那就合并这两个id所属的集合}}}// 2.记录每个id下面的邮箱。注意不能记录用户名下面的邮箱,因为可能有两个人重名Map<Integer, List<String>> idToEmail = new HashMap<>();     // 记录每个id有哪些邮箱for (Map.Entry<String, Integer> entry : emailToId.entrySet()) {int id = myUnion.find(entry.getValue());    // 找到该邮箱的idList<String> emails = idToEmail.getOrDefault(id, new ArrayList<>());emails.add(entry.getKey());idToEmail.put(id, emails);  // 把该邮箱放入id下面的邮箱集合}// 3.id换成用户名,并排序List<List<String>> result = new ArrayList<>();for (Map.Entry<Integer, List<String>> entry : idToEmail.entrySet()) {List<String> emails = entry.getValue();Collections.sort(emails);emails.add(0, accounts.get(entry.getKey()).get(0));     // 添加用户名result.add(emails);}return result;}
}
class UnionFind {// 用 Map 在存储并查集,表达的含义是 key 的父节点是 valueprivate Map<Integer,Integer> father;// 0.构造函数初始化public UnionFind(int n) {father = new HashMap<Integer,Integer>();for (int i = 0; i < n; i++) {father.put(i, null);}}// 1.添加:初始加入时,每个元素都是一个独立的集合,因此public void add(int x) {    // 根节点的父节点为nullif (!father.containsKey(x)) {father.put(x, null);}}// 2.查找:反复查找父亲节点。public int find(int x) {int root = x;    // 寻找x祖先节点保存到root中while(father.get(root) != null){root = father.get(root);}while(x != root){ // 路径压缩,把x到root上所有节点都挂到root下面int original_father = father.get(x);   // 保存原来的父节点father.put(x,root);      // 当前节点挂到根节点下面x = original_father; // x赋值为原来的父节点继续执行刚刚的操作}return root;}// 3.合并:把两个集合合并为一个,只需要把其中一个集合的根节点挂到另一个集合的根节点下方public void union(int x, int y) {   // x的集合和y的集合合并int rootX = find(x);int rootY = find(y);if (rootX != rootY){   // 节点联通只需要一个共同祖先,无所谓谁是根节点father.put(rootX,rootY);}}// 4.判断:判断两个元素是否同属一个集合public boolean isConnected(int x, int y) {return find(x) == find(y);}
}

Reference

[1]力扣721.账户合并:https://leetcode-cn.com/problems/accounts-merge/

欢迎关注公众号,回复801获取《算法笔记》pdf。每天分享大数据开发面经。

【算法】并查集(Java)相关推荐

  1. UVA10034 Freckles【Kruskal算法+并查集】

    In an episode of the Dick Van Dyke show, little Richie connects the freckles on his Dad's back to fo ...

  2. [基础算法] 并查集

    并查集 1.将两个集合合并 2.查询两个集合是否在同一个集合中 基本原理:每个集合用同一棵树来表示,树根的编号就是树的编号,每个节点储存其父节点,p[x]表示x的父节点. 问题一:如何判断是否为根节点 ...

  3. java并查集_一个非常实用而且精妙的算法-并查集(java语言实现)

    在学习数据结构的时候,老师多少会提到并查集,他的应用也是超级广泛.本文首先会通过案例来对并查集有一个介绍.然后给出并查集的java实现. 一.并查集原理 话说在江湖上有很多门派,这些门派相互争夺武林霸 ...

  4. 还是畅通工程(克鲁斯卡尔算法+并查集)

    还是畅通工程 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submis ...

  5. HDU1875 畅通工程再续【Kruskal算法+并查集】

    畅通工程再续 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Sub ...

  6. HDU1233 还是畅通工程【Kruskal算法+并查集】

    还是畅通工程 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  7. HDU1863 畅通工程【Kruskal算法+并查集】

    畅通工程 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  8. 最小生成树kruskal算法并查集版 C语言实现

    今天数据结构课讲了最小生成树的Kruskal算法和Prim算法,不过都只是概念,可能是怕他们听不懂吧,反正算法实现一概不讲...囧 下午抱着<算法导论>跑去图书馆看Kruskal算法,发现 ...

  9. 数据结构与算法——并查集(不相交集合)

    文章目录 认识并查集 并查集解析 基本思想 如何查看a,b是否在一个集合? a,b合并,究竟是a的祖先合并在b的祖先上,还是b的祖先合并在a上? 其他路径压缩? 代码实现 结语 认识并查集 对于并查集 ...

  10. vb6实现union数据结构_数据结构与算法——并查集(不相交集合)

    首发公众号:bigsai 认识并查集 对于并查集(不相交集合),很多人会感到很陌生,没听过或者不是特别了解.实际上并查集是一种挺高效的数据结构.实现简单,只是所有元素统一遵从一个规律所以让办事情的效率 ...

最新文章

  1. python利用wx.grid网格显示数据
  2. Windows系统运维转linux系统运维的经历
  3. mysql中设置字符集语句_mysql设置字符集
  4. python主线程有两个子线程、创建两个主函数_Python多任务之线程
  5. win8计算机丢失xinput1+3.dll,xinput1 3.dll丢失怎么办 win8下xinput1 3.dll丢失解决方法
  6. Java输出特定时间段特定格式时间信息
  7. Android进入商店并跳转到指定应用
  8. IOT(10)--RTOS
  9. 51单片机c语言程序控制,51单片机C语言编程基础及实例.pdf
  10. 初学JAVA随记——构造方法
  11. dev.c drv.c bus.c
  12. 九、全面提高人民生话水平
  13. Talib技术因子详解(七)
  14. java实现微博热搜榜_微博热搜数据
  15. 计算机排版系统程序,《计算机排版系统.doc
  16. SQL注入常用WAF绕过姿势
  17. 英语背单词软件需求分析
  18. 区块链如何确认记账权?
  19. html防止浏览器表单自动填充,禁止浏览器input表单自动填充
  20. Java 程序是如何执行的

热门文章

  1. 智慧城管系统提高城市综合管理水平
  2. Ubuntu 16.04-codeblocks 汉化
  3. 收货地址列表html,收货地址.html
  4. c语言教师工资管理系统源程序设计,C语言教师工资管理系统
  5. redis实现CAS
  6. java treetable_00035-layui+java 树形表格treeTable(异步请求)
  7. (转)量化投资发展史:野蛮、乱象、科学
  8. oppoa79支持手机html,oppo a79驱动
  9. matlab添加坐标,Matlab绘图添加直角坐标轴
  10. web前端笔试题整合