力扣题解990.等式方程的可满足性
思路:由于相等关系具有传递性,所有相等的变量属于同一个集合;只关心连通情况,不关心距离,因此很容易想到并查集。
设计算法流程:
- 扫描所有等式,将等式两边的顶点进行合并;
- 扫描所有等式,检查每一个不等式的两个顶点是不是在一个连通分量里:
- 如果在,返回false表示等式方程有矛盾
- 如果所有检查没有矛盾,返回true
并查集(union-find algorithm,Disjoint Sets):
-
- 并查集用于判断一对元素是否相连,它们的关系是动态添加的,这一类问题叫作“动态连通性”问题
- 主要支持“合并”与“查询是否在同一个集合”操作;
- 底层结构是“数组”或者“哈希表”,用于表示“结点”指向的“父结点”,初始化时指向自已;
- “合并”就是把一个集合的根结点指向另一个集合的根结点,只要根结点不一样,就表示在同一个集合里;
- 这种表示“不相交集合”的方法称之为“代表元法”,以每个结点的根结点作为一个集合的“代表元”
- 并查集的应用:最小生成树,kruskal算法
每个节点都保存了到节点的引用
并查集的优化:路径压缩(Path Compression)与按“秩(rank)”合并
路径压缩是指在查询的过程中,更改节点的指向,使得树的高度更低,一般而言,有“隔代压缩”和“完全压缩”两种策略。
按秩合并是指在合并的过程中,使得“高度”更低 的树的根结点指向高度更高的根结点,以避免合并以后的树高度增加
路径压缩和按秩合并一起使用的时候,难以维护“秩”准确的定义,但依然具有参考价值。
并查集同时使用“路径压缩”和“按秩合并”,“合并”与“查询”的时间复杂度接近O(1);
public class UnionFind {
private int[] parent;
public UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) { //每个节点赋值给自已,表示每个节点是单独的集合
parent[i] = i;
}
}
/**
*
* @param x
* @return 返回根节点
*/
public int find(int x) {
while (x != parent[x]) {
parent[x] = parent[parent[x]]; //路径压缩
x = parent[x];
}
return x;
}
/**
* 如果合并成功,返回true
* @param x
* @param y
*/
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
parent[rootX] = rootY;
}
public boolean isConnected(int x, int y) {
return find(x) == find(y) ;
}
}
力扣中并查集相关的题目:
- 547:朋友圈
- 200:岛屿数量
- 684:冗余连接
- 1319:连通网络的操作次数
- 399:除法求值
- 952:按公因数计算最大组件大小