文章目录

  • 并查集基础知识
    • 定义
    • C++实现
    • 优化
  • 精选算法题(Java实现)
    • 实现并查集
    • 交换字符串中的元素
    • 最长连续序列 - 字节面试常考
    • 连通网络的操作次数
    • 最大岛屿数量 (三种解法)
    • 省份数量
    • 冗余连接
    • 冗余连接Ⅱ
    • 情侣牵手(困难)
    • 移除最多的同行或同列石头
    • 等式方程的可满足性
  • 结语

并查集基础知识

定义

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。

主要构成:
并查集主要由一个整型数组pre[ ]和两个函数find( )、join( )构成。
数组 pre[ ] 记录了每个点的前驱节点是谁,函数 find(x) 用于查找指定节点 x 属于哪个集合,函数 join(x,y) 用于合并两个节点 x 和 y 。

作用:
并查集的主要作用是求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)

C++实现

class UnionFind{public:UnionFind(int n){size = n;father = new int[n];for(int i = 0; i < n; i++){father[i] = i;}}//查看x的根节点 -> x所属集合int find(int x){int root = x;while(root != father[root]){root = father[root];}return root;}//将x和y染色为同一个颜色 -> 合并x和y的所属集合void merge(int x, int y){int rootX = find(x);int rootY = find(y);if(rootX == rootY) return;father[rootX] = rootY;//father[rooY] = rootX;}//路径压缩优化的查找int find(int x){int root = x;while(root != father[root]){root = father[root];}while(x != root){int fx = father[x];father[x] = root;x = fx;}return root;}//针对节点数量优化void merge(int x, int y){int rootX = find(x);int rootY = find(y);if(rootX == rootY) return;if(treeSize[rootX] < treeSize[rootY]){father[rootX] = rootY;treeSize[rootY] += treeSize[rootX];}else{father[rootY] = rootX;treeSize[rootX] += treeSize[rootY];}}
public:int *father, *treeSize, size;
};int P(UnionFind uf){for(int i = 0; i < uf.size; i++{cout << uf.color[i] << " ";} cout << endl;
}
int main(){int n = 10;UnionFind uf(n);uf.merge(0,1);P(uf);uf.merge(1,2);P(uf);uf.merge(5,9);P(uf);uf.merge(7,8);P(uf);uf.merge(8,6);P(uf);uf.merge(1,3);P(uf);uf.merge(6,1);P(uf);return 0;
}

问题思考:

  1. 极端情况下会退化成一条链
  2. 将结点数量多的接到结点少的树上,导致了退化
  3. 将较高的树接到较低的树上,导致了退化

面试一般要求优化到这一步
LeetCode547 省份数量

优化

精选算法题(Java实现)

实现并查集

/*** 实现并查集* @author: William* @time:2022-04-08*/
public class UnionFind {//记录每个节点的根节点int[] parent;//记录每个子集的节点数int[] rank;//记录并查集中的连通分量数量int count;public UnionFind1(int n) {count = n;parent = new int[n];for(int i = 0; i < n; i++) {parent[i] = i;}rank = new int[n];Arrays.fill(rank, 1);}public int find(int i) {if(parent[i] != i) parent[i] = find(parent[i]);return parent[i];}public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX != rootY) {if(rank[rootX] > rank[rootY]) {parent[rootY] = rootX;}else if(rank[rootX] < rank[rootY]) {parent[rootX] = rootY;}else {parent[rootY] = rootX;//相等的情况rank[rootX] += 1;}count--;//维护数量}}public int getCount() {return count;}public boolean connected(int x, int y) {return find(x) == find(y);}
}

交换字符串中的元素

/*** 交换字符串中的元素* @author: William* @time:2022-04-12*/
public class Num1202 {public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {if(pairs.size() == 0) {return s;}//1. 将任意交换的结点对输入并查集int n = s.length();UnionFind uf = new UnionFind(n);for(List<Integer> pair : pairs) {uf.union(pair.get(0), pair.get(1));}//2. 构建映射关系//char[] charArray = s.toCharArray();// key:连通分量的代表元,value:同一个连通分量的字符集合(保存在一个优先队列中)Map<Integer, PriorityQueue<Character>> map = new HashMap<>(n);for(int i = 0; i < n; i++) {int root = uf.find(i);
//          if (map.containsKey(root)) {//              hashMap.get(root).offer(charArray[i]);
//          } else {//              PriorityQueue<Character> minHeap = new PriorityQueue<>();
//              minHeap.offer(charArray[i]);
//              hashMap.put(root, minHeap);
//          }// 上面六行代码等价于下面一行代码,JDK 1.8 以及以后支持下面的写法map.computeIfAbsent(root,  key -> new PriorityQueue<>()).offer(s.charAt(i));}//3. 重组字符串StringBuilder sb  = new StringBuilder();for(int i = 0; i < n; i++) {int root = uf.find(i);sb.append(map.get(root).poll());}return sb.toString();}private class UnionFind {private int[] parent;private int[] rank;public UnionFind(int n) {this.parent = new int[n];this.rank = new int[n];for(int i = 0; i < n; i++) {this.parent[i] = i;this.rank[i] = 1;}}public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX != rootY) {if(rank[rootX] > rank[rootY]) {parent[rootY] = rootX;}else if(rank[rootX] < rank[rootY]) {// 此时以 rootY 为根结点的树的高度不变parent[rootX] = rootY;}else {parent[rootY] = rootX;// 此时以 rootX 为根结点的树的高度仅加了 1rank[rootX]++;}}}public int find(int x) {if(parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}}
}

最长连续序列 - 字节面试常考

/*** 最长连续序列 - 字节面试常考* @author: William* @time:2022-04-14*/
public class Num128 {public int longestConsecutive(int[] nums) {int ans = 0;//用来筛选某个数的左右连续数是否存在Map<Integer, Integer> map = new HashMap<>();//将连续的数字组成一个个集合UnionFind uf = new UnionFind(nums.length);for(int i = 0; i < nums.length; i++) {if(map.containsKey(nums[i])) continue;if(map.containsKey(nums[i] - 1)) {//往左判断uf.union(i, map.get(nums[i] - 1));}if(map.containsKey(nums[i] + 1)) {//往右判断uf.union(i, map.get(nums[i] + 1));}map.put(nums[i], i);//存储当前数}for(int i = 0; i < nums.length; i++) {//选出最长的数if(uf.find(i) != i) continue;//不是根节点ans = Math.max(ans, uf.rank[i]);}return ans;}class UnionFind{int[] parent;int[] rank;public UnionFind(int n) {this.parent = new int[n];this.rank = new int[n];for(int i = 0; i < n; i++) {this.parent[i] = i;this.rank[i] = 1;}}//这一步很关键 当时写的其他方法失败了public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX != rootY) {if(rank[rootX] < rank[rootY]) {int tmp = rootX;rootX = rootY;rootY = tmp;}parent[rootY] = rootX;rank[rootX] += rank[rootY];}}public int find(int x) {if(parent[x] != x){parent[x] = find(parent[x]);} return parent[x];}}
}

连通网络的操作次数

/*** 连通网络的操作次数* @author: William* @time:2022-04-14*/
public class Num1319 {public int makeConnected(int n, int[][] connections) {//网线数量太少的情况 n是电脑数if(connections.length < n - 1) return -1;UnionFind uf = new UnionFind(n);for(int[] connection : connections) {uf.union(connection[0], connection[1]);}//只需要操作连通数量-1次即可return uf.getCount() - 1;}class UnionFind{int count;int[] parent;int[] rank;public UnionFind(int n) {this.count = n;this.parent = new int[n];this.rank = new int[n];for(int i = 0; i < n; i++) {this.parent[i] = i;this.rank[i] = 1;}}public int getCount() {return count;}public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX != rootY) {if(rank[rootX] > rank[rootY]) {parent[rootY] = rootX;}else if(rank[rootX] < rank[rootY]) {parent[rootX] = rootY;}else {parent[rootY] = rootX;rank[rootX]++;}count--;}}public int find(int x) {return parent[x] == x ? x : find(parent[x]); }}
}

最大岛屿数量 (三种解法)

/*** 最大岛屿数量* @author: William* @time:2022-04-10*/
public class Num200 {class UnionFind{int count;int[] parent;int[] rank;public UnionFind(char[][] grid) {count = 0;int m = grid.length;//行数int n = grid[0].length;//列数parent = new int[m * n];rank = new int[m * n];for(int i = 0; i < m; i++) {for(int j = 0; j < n; j++) {if(grid[i][j] == '1') {parent[i * n + j] = i * n + j;//规律count++;}rank[i * n + j] = 0;}}}public int find(int i) {if(parent[i] != i) parent[i] = find(parent[i]);return parent[i];}public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX != rootY) {if(rank[rootX] > rank[rootY]) {parent[rootY] = rootX;}else if(rank[rootX] < rank[rootY]) {parent[rootX] = rootY;}else {parent[rootY] = rootX;//相等的情况rank[rootX] += 1;}count--;//维护数量}}public int getCount() {return count;}}public int numIslands(char[][] grid) {if (grid == null || grid.length == 0) {return 0;}int nr = grid.length;int nc = grid[0].length;int num_islands = 0;UnionFind uf = new UnionFind(grid);for (int r = 0; r < nr; ++r) {for (int c = 0; c < nc; ++c) {if (grid[r][c] == '1') {grid[r][c] = '0';if (r - 1 >= 0 && grid[r-1][c] == '1') {uf.union(r * nc + c, (r-1) * nc + c);}if (r + 1 < nr && grid[r+1][c] == '1') {uf.union(r * nc + c, (r+1) * nc + c);}if (c - 1 >= 0 && grid[r][c-1] == '1') {uf.union(r * nc + c, r * nc + c - 1);}if (c + 1 < nc && grid[r][c+1] == '1') {uf.union(r * nc + c, r * nc + c + 1);}}}}return uf.getCount();}//dfs —— 重点掌握public int numIslands1(char[][] grid) {int count = 0;for(int i = 0; i < grid.length; i++) {//行数for(int j = 0; j < grid[0].length; j++) {//列数if(grid[i][j] == '1') {//满足条件就继续递归dfs(grid, i, j);count++;}}}return count;}private void dfs(char[][] grid, int i, int j) {//终止条件if(i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] == '0') return;grid[i][j] = '0';//分别向上下左右递归dfs(grid, i + 1, j);dfs(grid, i, j + 1);dfs(grid, i - 1, j);dfs(grid, i, j - 1);}//bfspublic int numIslands2(char[][] grid) {int count = 0;for(int i = 0; i < grid.length; i++) {for(int j =0; j < grid[0].length; j++) {if(grid[i][j] == '1') {bfs(grid, i, j);count++;}}}return count;}private void bfs(char[][] grid, int i, int j) {Queue<int[]> list = new LinkedList<>();list.add(new int[] {i, j});while(!list.isEmpty()) {int[] cur = list.remove();i = cur[0]; j = cur[1];if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {grid[i][j] = '0';list.add(new int[] {i + 1, j});list.add(new int[] {i - 1, j});list.add(new int[] {i, j + 1});list.add(new int[] {i, j - 1});}}}
}

省份数量

/*** 省份数量* @author: William* @time:2022-04-11*/
public class Num547 {public int findCircleNum(int[][] isConnected) {int provinces = isConnected.length;int[] parent = new int[provinces];//开辟一个parent数组 储存 某个节点的父节点for(int i =0; i < provinces;i++){parent[i] = i;}for(int i = 0; i < provinces; i++){for(int j = i + 1; j < provinces; j++){//两个节点只要是连通的就合并if(isConnected[i][j] == 1){union(parent, i, j);}}}int numProvinces = 0;//扫描parent数组 如果当前节点对应根节点 就是一个省份for(int i = 0; i < provinces; i++){if(parent[i] == i){numProvinces++;}}return numProvinces;}//支持路径压缩的查找函数public int find(int[] parent, int index){//父节点不是自己if(parent[index] != index){//递归调用查找函数 并把当前结果储存在当前节点父节点数组中parent[index] = find(parent, parent[index]);}//当父节点是本身时return parent[index];}//合并函数public void union(int[] parent, int index1, int index2){parent[find(parent , index1)] = find(parent, index2);}//dfspublic int findCircleNum1(int[][] isConnected) {int provinces = isConnected.length;boolean[] visited = new boolean[provinces];int numProvinces = 0;for(int i = 0; i < provinces; i++) {//如果该城市未被访问过,则从该城市开始深度优先搜索if(!visited[i]) {dfs(isConnected, visited, provinces, i);numProvinces++;}}return numProvinces;}private void dfs(int[][] isConnected, boolean[] visited, int provinces, int i) {for(int j = 0; j < provinces; j++) {//j时与i相连的邻居节点,相连且未被访问到if(isConnected[i][j] == 1 && !visited[j]) {visited[j] = true;//继续做深度优先搜索dfs(isConnected, visited, provinces, j);}}}
}

冗余连接

/*** 冗余连接* @author: William* @time:2022-04-11*/
public class Num684 {public int[] findRedundantConnection(int[][] edges) {int n = edges.length;int[] parent = new int[n + 1];for(int i = 1; i <= n; i++) {parent[i] = i;}for(int i = 0; i < n; i++) {int[] edge = edges[i];int node1 =edge[0], node2 = edge[1];//说明两个顶点不连通,当前边不会导致环出现if(find(parent, node1) != find(parent, node2)) {union(parent, node1, node2);}else {//已经连通成环 返回该边即可return edge;}}//这种情况表示没有return new int[0];}public void union(int[] parent, int x, int y) {parent[find(parent, x)] = find(parent, y);}public int find(int[] parent, int x) {if(parent[x] != x) {parent[x] = find(parent, parent[x]);}return parent[x];}
}

冗余连接Ⅱ

/*** 冗余连接Ⅱ  hard* @author: William* @time:2022-04-11*/
public class Num685 {public int[] findRedundantDirectedConnection(int[][] edges) {int n = edges.length;UnionFind uf = new UnionFind(n + 1);int[] parent = new int[n + 1];for (int i = 1; i <= n; ++i) {parent[i] = i;}int conflict = -1;int cycle = -1;for (int i = 0; i < n; ++i) {int[] edge = edges[i];int node1 = edge[0], node2 = edge[1];if (parent[node2] != node2) {conflict = i;} else {parent[node2] = node1;if (uf.find(node1) == uf.find(node2)) {cycle = i;} else {uf.union(node1, node2);}}}if (conflict < 0) {int[] redundant = {edges[cycle][0], edges[cycle][1]};return redundant;} else {int[] conflictEdge = edges[conflict];if (cycle >= 0) {int[] redundant = {parent[conflictEdge[1]], conflictEdge[1]};return redundant;} else {int[] redundant = {conflictEdge[0], conflictEdge[1]};return redundant;}}}
}class UnionFind{int[] parent;public UnionFind(int n) {parent = new int[n];for(int i = 0; i < n; i++) {parent[i] = i;}}public void union(int x, int y) {parent[find(x)] = find(y);}public int find(int x) {if(parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}
}

情侣牵手(困难)

/*** 情侣牵手 hard* @author: William* @time:2022-04-12*/
public class Num765 {//如果一对情侣恰好坐在了一起,并且坐在了成组的座位上,//其中一个下标一定是偶数,另一个一定是奇数,并且偶数的值 + 1 = 奇数的值。//例如编号数对 [2, 3]、[9, 8],//这些数对的特点是除以 2(下取整)得到的数相等。public int minSwapsCouples(int[] row) {int len = row.length;int N = len >> 1;UnionFind uf = new UnionFind(N);for(int i = 0; i < len; i += 2) {uf.union(row[i] >> 1, row[i + 1] >> 1);}return N - uf.getCount();}private class UnionFind {private int[] parent;private int count;public int getCount() {return count;}public UnionFind(int n) {this.count = n;this.parent = new int[n];for(int i = 0; i < n; i++) {parent[i] = i;}}public int find(int x) {if(parent[x] != x) {parent[x] = find(parent[x]);}return parent[x];}public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX == rootY) return;parent[rootX] = rootY;count--;}}
}

移除最多的同行或同列石头

/*** 移除最多的同行或同列石头* @author: William* @time:2022-04-14*/
public class Num947 {public int removeStones(int[][] stones) {int sum = stones.length;UnionFind uf = new UnionFind(sum);for(int i = 0; i < sum; i++) {//j 是 i 下一个石头for(int j = i + 1; j < sum; j++) {int x1 = stones[i][0], y1 = stones[i][1];int x2 = stones[j][0], y2 = stones[j][1];if(x1 == x2 || y1 == y2) {//处于同行或同列uf.union(i, j);//粉碎石头}}}return sum - uf.getCount(); }class UnionFind{int count;int[] parent;int[] rank;public UnionFind(int n) {this.count = n;this.parent = new int[n];this.rank = new int[n];for(int i = 0; i < n; i++) {this.parent[i] = i;this.rank[i] = 1;}}public int getCount() {return count;}public void union(int x, int y) {int rootX = find(x), rootY = find(y);if(rootX != rootY) {if(rank[rootX] < rank[rootY]) {int tmp = rootX;rootX = rootY;rootY = tmp;}parent[rootY] = rootX;rank[rootX] += rank[rootY];count--;}}public int find(int x) {return parent[x] == x ? x : find(parent[x]); }}
}

等式方程的可满足性

/*** 等式方程的可满足性* @author: William* @time:2022-04-11*/
public class Num990 {public boolean equationsPossible(String[] equations) {int[] parent = new int[26];for(int i = 0; i < 26; i++) {parent[i] = i;}for(String str : equations) {if(str.charAt(1) == '=') {int x = str.charAt(0) - 'a';int y = str.charAt(3) - 'a';union(parent, x, y);}}for(String str : equations) {if(str.charAt(1) == '!') {int x = str.charAt(0) - 'a';int y = str.charAt(3) - 'a';//说明连过了if(find(parent, x) == find(parent, y)) {return false;}}}return true;}public void union(int[] parent, int x, int y) {parent[find(parent, x)] = find(parent, y); }public int find(int[] parent, int x) {while(parent[x] != x) {parent[x] = parent[parent[x]];x = parent[x];}return x;}
}

结语

并查集对我们来说是一个模板,无论理解还是不理解,都应该在笔试的时候可以快速写出来,很多时候看起来与连接有关的题,找到规律之后都能用并查集快速解出来

并查集(Union-Find) (图文详解)相关推荐

  1. 大数据学习系列之七 ----- Hadoop+Spark+Zookeeper+HBase+Hive集群搭建 图文详解

    引言 在之前的大数据学习系列中,搭建了Hadoop+Spark+HBase+Hive 环境以及一些测试.其实要说的话,我开始学习大数据的时候,搭建的就是集群,并不是单机模式和伪分布式.至于为什么先写单 ...

  2. 并查集的相关知识详解 Come baby!!!

    一:并查集的相关知识 这道题用到了并查集,所以我就学了一下并查集,所以把自己的见解也分享给大家(建议 先看视频 再浏览 博客 再自己敲一遍 学习效率高而已,我总是乱着来 以为看几篇博客就会了,其实最后 ...

  3. c语言 多线程 传话,code vs1506传话(塔尖)+tarjan图文详解

    1506 传话 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 白银 Silver 题目描述 Description 一个朋友网络,如果a认识b,那么如果a第一次收到某个消息,那么会 ...

  4. 给Clouderamanager集群里安装基于Hive的大数据实时分析查询引擎工具Impala步骤(图文详解)...

    不多说,直接上干货! 这个很简单,在集群机器里,选择就是了,本来自带就有Impala的. 扩展博客 给Ambari集群里安装基于Hive的大数据实时分析查询引擎工具Impala步骤(图文详解) 欢迎大 ...

  5. HUE配置文件hue.ini 的hbase模块详解(图文详解)(分HA集群和非HA集群)

    不多说,直接上干货! 我的集群机器情况是 bigdatamaster(192.168.80.10).bigdataslave1(192.168.80.11)和bigdataslave2(192.168 ...

  6. 二叉树合集(二):霍夫曼树(图文详解)

    合集地址 二叉树合集(一):二叉树基础(含四种遍历,图文详解) 二叉树合集(二):霍夫曼树(图文详解) 二叉树合集(三):线索二叉树(图文详解) 二叉树合集(四):对称二叉树(递归和迭代实现) 二叉树 ...

  7. 【图文详解】Android手机系统精简 搭建Linux集群硬件环境 Jdk运行HelloWorld

    1.序言 经过多日的折腾,终于有点小小成就!期间反反复复,多次尝试,有好几次都有了放弃的念头:但最终还是坚持了下来,仔细学习研究的文章有好几十篇,各种工具到处找.为了总结经验教训,也为了有同样需求的朋 ...

  8. 怎么查计算机网络硬件配置,怎么样查看电脑配置?5种方法查看电脑硬件配置好坏图文详解...

    学会看电脑配置,其实对于我们来说十分重要,因为电脑硬件配置的好坏,是直接影响到我们实际操作的.而查看电脑配置的方法有很多,比如说命令查看,系统查看,诊断程序查看,你还可以使用第三方软件查看.下面,电脑 ...

  9. 【图文详解】搭建 Spring Authorization oauth2-server-resource-client-gateway-eureka 完整Demo

    1.项目概述 1.1.概述 本项目是在前面章节的基础上,进行的升级改造.增加了注册中心.网关,更加贴近于实际需求. 在进行本节之前,请先搭建前面项目: [图文详解]搭建 Spring Authoriz ...

  10. Docker持续部署图文详解

    2019独角兽企业重金招聘Python工程师标准>>> Docker持续部署图文详解 博客分类: docker 架构 JAVA项目如何通过Docker实现持续部署(只需简单四步),即 ...

最新文章

  1. 微信小程序 -字体图标
  2. Java判断文件是否为图片文件(GIF,PNG,JPG)
  3. Android(3_2)-----模仿微信界面:通讯录页面
  4. html3d变形,深入理解CSS变形transform(3d) - 小火柴的蓝色理想
  5. 米洛个人修炼术:注意这三方面,天天都早起
  6. C++中“强制转换”的四大天王
  7. eclipse 使用问题
  8. psp3000 java_psp上的python
  9. 计算机软件销售适用增值税税率,计算机软件研发企业要交什么税,税率是多少...
  10. Redis的使用场景及其介绍
  11. [日常] 修改编辑word中的页眉页脚
  12. android通话模块详解
  13. DNS服务器未响应惊叹号,怎么办呢电脑连接网络出现感叹号?
  14. 直播商城源码,商城开发实现商城底部导航栏
  15. KMS客户端出现0xC004F074错误的解决方法
  16. 预备内容:---软件安装篇(1)
  17. create-react-app : 无法加载文件 C:\Users\MyPC\AppData\Roaming\npm\create-react-app.ps1,因为 在此系统上禁止运行—问题解决方法
  18. 告别传智,重新上路----近5年来的总结及未来5年的计划
  19. Tita OKRs-E 使OKR成为您企业DNA一部分
  20. ANSYS | 螺栓模拟

热门文章

  1. 如何完美快速地卸载office2007,2010,2013,2016
  2. java元数据的概念_元数据 概念及分类
  3. XAF将ListView和DetailView一起显示
  4. 总线(四)Modbus总线 协议
  5. HTML <div> 标签的 align 属性 align=“absmiddle“
  6. SQL Server 扩展秘钥管理(EKM)
  7. SAP财务凭证常见的冲销步骤详细操作手册(FB08、AB08、VF11、FBRA等)
  8. boost哪些库需要编译
  9. Framer多车型切换实现的网络功能
  10. 【全志T113-S3_100ask】15-2 linux系统gpio模拟spi驱动屏幕——ILI9341