并查集介绍及实现
这道题是一道对于无向图的连通问题的判断。有一个典型的数据结构可以解决这类问题 – 并查集
并查集 - 基本概念
- 并查集是一种数据结构
- 并(Union): 代表合并
- 查(Find): 代表查询
- 集(Set): 代表这是以字典为基础数据结构。它的基本功能是合并集合中的元素,查找集合中的元素。
- 并查集的典型应用就是有关连通分量的问题
- 并查集解决单个问题(添加,合并,查找)的时间复杂度都是O(1)
并查集 - 实现
- 数据结构:并查集类似于树结构,但是与树结构相反。在并查集中,每个节点会记录其对应的父节点。
// 记录 当前节点 及其 父节点
private Map<Integer, Integer> father;
如果两个节点是相互连通的,那么他们在同一个树中,或者说他们在同一个集合中,即他们具有相同的祖先 (上图中,7和3是连通的,但是7和8则不连通)
- 添加元素:讲一个新节点添加到并查集中,此时它的父节点应该为空,即它是独立的节点
// 当把一个新节点添加到并查集中,它的父节点应该为空
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
numOfSets++;
}
}
- 合并两个节点:如果发现两个节点是连通的,就要把他们合并,即他们的祖先是相同的。(因为是无向图,所以父节点是谁无所谓)
// 如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。
// 这里究竟把谁当做父节点一般是没有区别的。
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
father.put(rootX, rootY);
numOfSets--;
}
}
- 判断两节点是否连通:如果两个节点的祖先相同,则他们就是连通的
// 我们判断两个节点是否处于同一个连通分量的时候,就需要判断它们的祖先是否相同
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
- 查找祖先(重点):如果当前节点的父节点不为空,则迭代向上找,知道父节点为空时,说明到达了祖先节点
// 查找祖先的方法是:如果节点的父节点不为空,那就不断迭代。
public int find(int x) {
int root = x;
while (father.get(root) != null) {
root = father.get(root);
}
return root;
}
并查集基本模版(java)
class UnionFind {
// 记录 当前节点 及其 父节点
private Map<Integer, Integer> father;
// 记录集合的数量
private int numOfSets = 0;
public UnionFind() {
father = new HashMap<>();
numOfSets = 0;
}
// 当把一个新节点添加到并查集中,它的父节点应该为空
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
numOfSets++;
}
}
// 如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。这里究竟把谁当做父节点一般是没有区别的。
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
father.put(rootX, rootY);
numOfSets--;
}
}
// 我们判断两个节点是否处于同一个连通分量的时候,就需要判断它们的祖先是否相同
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
// 查找祖先的方法是:如果节点的父节点不为空,那就不断迭代。
public int find(int x) {
int root = x;
while (father.get(root) != null) {
root = father.get(root);
}
return root;
}
// 返回连通分量数量
public int getNumOfSets() {
return numOfSets;
}
}
思路
- 本体要考察的就是连通分量的数量,我们增加一个返回连通分量数量的方法(有多少个集合或者说树)
- 当我们发现两个城市有连通时,就将两个分量连接在一起,合并到一个集合中
- 增加分量的时候,分量数量加一;合并的时候,分量数量减一
实现代码(java)
class Solution {
class UnionFind {
// 记录 当前节点 及其 父节点
private Map<Integer, Integer> father;
// 记录集合的数量
private int numOfSets = 0;
public UnionFind() {
father = new HashMap<>();
numOfSets = 0;
}
// 当把一个新节点添加到并查集中,它的父节点应该为空
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
numOfSets++;
}
}
// 如果发现两个节点是连通的,那么就要把他们合并,也就是他们的祖先是相同的。这里究竟把谁当做父节点一般是没有区别的。
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
father.put(rootX, rootY);
numOfSets--;
}
}
// 我们判断两个节点是否处于同一个连通分量的时候,就需要判断它们的祖先是否相同
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
// 查找祖先的方法是:如果节点的父节点不为空,那就不断迭代。
public int find(int x) {
int root = x;
while (father.get(root) != null) {
root = father.get(root);
}
return root;
}
public int getNumOfSets() {
return numOfSets;
}
}
public int findCircleNum(int[][] isConnected) {
UnionFind uf = new UnionFind();
for (int i = 0; i < isConnected.length; i++) {
uf.add(i);
for (int j = 0; j < i; j++) {
if (isConnected[i][j] == 1) {
uf.merge(i, j);
}
}
}
return uf.getNumOfSets();
}
}