在个人博客查看本文
并查集要处理的两个问题
- 查询
- 合并
设计并查集的两种思想
- 基于id
id 不同则来自不同的集合 。
合并时需要将其中一个集合中的所有元素的 id 赋值成为另一集合的 id。需遍历整个一个集合的 id 。
- 基于parents
parent[i] 表示标识为 i 的结点的父亲结点的标识(可以形象地记为「找爸爸」) 。在这个定义下,根结点的父亲结点是自己 。这种方式形成的「并查集」组织成了 若干个不相交的树形结构,并且我们在访问结点的时候,总是按照「从下到上」进行访问的 。代表元法「代表元」的「三个不重要」
- 谁作为根结点不重要:根结点与非根结点只是位置不同,并没有附加的含义;
- 树怎么形成的不重要:合并的时候任何一个集合的根结点指向另一个结合的根结点就可以;
- 树的形态不重要:理由同「谁作为根结点不重要」 。
代表元法可能造成的问题
树的高度过高,查询性能降低 。
解决方案有「按秩合并」与「路径压缩」 。
这里的秩有两种含义:
size合并rank合并
按 rank 合并【【笔记】并查集---无向图处理代码模板及类型题】按
rank 合并的意思是让树的「高度」较小的树的根结点,指向树的「高度」较大的树的根结点 。之所以把「高度」称为 rank,是因为同时使用「按秩合并」和「路径压缩」的时候,树的「高度」很难维护其准确的定义,但是依然可以作为合并时候的依据,因此称为「秩」 。
路径压缩隔代压缩两步一跳,一直循环[把当前结点指向它的父亲结点的父亲结点]
完全压缩把从 [查询结点] 到 [根结点] 沿途经过的所有结点都指向根结点 。[完全压缩] 相比较于 [隔代压缩] 压缩的更彻底 。
代码模板
class UnionFind {int[] parent;int n;public UnionFind(int n) {this.n = n;this.parent = new int[n];for (int i = 0; i < n; ++i) {parent[i] = i;}}public int findset(int x) {return parent[x] == x ? x : (parent[x] = findset(parent[x]));}public void unite(int x, int y) {x = findset(x);y = findset(y);if (x == y) {return;}parent[y] = x;}} 并查集使用包括两种操作,查询和合并 。- 使用
findset方法来实现并查集查询 。parent[x] == x ? x : (parent[x] = findset(parent[x]))寻找 x 的父结点 。
parent[x] == x,否则我们将继续寻找 x 父亲的父节点 。即 parent[x] = findset(parent[x])- 受用
unite方法来实现集合的合并 。首先要确定两个元素是否在同一集合中,使用findset查找其是否有共同父节点即可 。
x = findset(index1);y = findset(index2);if (x == y) {return;}如果拥有共同父结点,则证明二者在一个集合中,无需合并 。如果无公共父结点,我们只需要覆盖另外元素的父结点即可 parent[y] = x 。基础问题等式方程的可满足性990. 等式方程的可满足性给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b" 或 "a!=b" 。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名 。
只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false 。
示例 1:
输入:
["a==b","b!=a"]输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程 。没有办法分配变量同时满足这两个方程 。
示例 2:
输入:
["b==a","a==b"]输出:true
解释:我们可以指定 a = 1 且 b = 1 以满足满足这两个方程 。
示例 3:
输入:
["a==b","b==c","a==c"]输出:true
示例 4:
输入:
["a==b","b!=c","c==a"]输出:false
示例 5:
输入:
["c==c","b==d","x!=z"]输出:true
提示:
1 <= equations.length <= 500equations[i].length == 4equations[i][0]和equations[i][3]是小写字母equations[i][1]要么是'=',要么是 `'!'``- ``equations[i][2]
是'='`
a==b ,b==a,b!=c 这类关系,我们可将== 视为联通关系,而!=视为非联通关系,我们只需要根据已知条件建立联通关系以及非联通关系,确定其中是否存在有矛盾关系即可 。至于并查集的建立,因为存在有 a,b,c,d 等小写字母,且题目变量有且只有 小写字母与 =,!= 因此我们建立的并查集中 parent[] 的长度为26.
题目代码如下:
class Solution {public boolean equationsPossible(String[] equations) {UnionFind uf = new UnionFind(26);for(String str : equations){if(str.charAt(1)=='='){int index1 = str.charAt(0) - 'a';int index2 = str.charAt(3) - 'a';uf.unite(index1, index2);}}for(String str : equations){if(str.charAt(1)=='!'){int index1 = str.charAt(0) - 'a';int index2 = str.charAt(3) - 'a';if(uf.findset( index1) ==uf.findset(index2)){return false;}}}return true;}}class UnionFind {int[] parent;int n;public UnionFind(int n) {this.n = n;this.parent = new int[n];for (int i = 0; i < n; ++i) {parent[i] = i;}}public int findset(int x) {return parent[x] == x ? x : (parent[x] = findset(parent[x]));}public void unite(int index1, int index2) {index1 = findset(index1);index2 = findset(index2);if (index1 == index2) {return;}parent[index2] = index1;}}省份数量省份数量有 n 个城市,其中一些彼此相连,另一些没有相连 。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连 。省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市 。
给你一个
n x n 的矩阵 isConnected,其中isConnected[i][j] = 1表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连 。返回矩阵中 省份 的数量 。
示例1:

文章插图
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]输出:2示例 2:
文章插图
输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]]输出:3提示:1 <= n <= 200n == isConnected.lengthn == isConnected[i].lengthisConnected[i][j]为 1 或 0isConnected[i][i]== 1isConnected[i][j] == isConnected[j][i]
思路即为:查询当前节点的父结点 。合并相同父结点的节点 。确定树的个数 。
代码如下:
class Solution {public int findCircleNum(int[][] isConnected) {int n = isConnected.length;UnionFind uf = new UnionFind(n);//查询以及合并集合for(int i = 0; i < n; i++){for(int j = i + 1; j < n; j++){if(isConnected[i][j]==1){uf.unite(i, j);}}}//计算树的个数int cnt = 0;for(int i = 0; i < n; i++){if(uf.parent[i] == i){cnt++;}}return cnt;}}class UnionFind {int[] parent;int n;public UnionFind(int n) {this.n = n;this.parent = new int[n];for (int i = 0; i < n; ++i) {parent[i] = i;}}public int findset(int x) {return parent[x] == x ? x : (parent[x] = findset(parent[x]));}public void unite(int index1, int index2) {index1 = findset(index1);index2 = findset(index2);if (index1 == index2) {return;}parent[index2] = index1;}}冗余链接684. 冗余连接树可以看成是一个连通且 无环 的 无向 图 。给定往一棵
n 个节点 (节点值 1~n) 的树中添加一条边后的图 。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边 。图的信息记录于长度为 n 的二维数组 edges,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边 。请找出一条可以删去的边,删除后可使得剩余部分是一个有着
n 个节点的树 。如果有多个答案,则返回数组 edges 中最后出现的边 。示例 1:

文章插图
输入: edges = [[1,2], [1,3], [2,3]]输出: [2,3]示例 2:
文章插图
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]输出: [1,4]提示:n == edges.length3 <= n <= 1000edges[i].length == 21 <= ai < bi <= edges.lengthai != biedges中无重复元素- 给定的图是连通的
edges找出最后那条使得无向图成环的边 。考虑环是怎么形成的:
如果两个顶点属于不同的连通分量(即不同集合),则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量 。
如果两个顶点属于相同的连通分量(同一集合),则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回 。
值得注意的是,我们的边是从1开始到n 因此在建立
parent[]时,要将其长度设为 n+1节点内容分别为 1~n 。代码如下:
class Solution {public int[] findRedundantConnection(int[][] edges) {int n = edges.length;UnionFind uf = new UnionFind(n);for(int i = 0; i < n; i++){int[] edge = edges[i];int node1 = edge[0], node2 = edge[1];if(uf.findset(node1)!= uf.findset(node2)){uf.unite(node1, node2);}else{return edge;}}return new int[0];}}class UnionFind {int[] parent;public UnionFind(int n) {this.parent = new int[n+1];for (int i = 0; i <= n; ++i) {parent[i] = i;}}public int findset(int x) {return parent[x] == x ? x : (parent[x] = findset(parent[x]));}public void unite(int x, int y) {x = findset(x);y = findset(y);if (x == y) {return;}parent[y] = x;}}
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
