并查集功能
- 有若干个样本a、b、c、d…类型假设是V
- 在并查集中一开始认为每个样本都在单独的集合里
- 用户可以在任何时候调用如下两个方法:
boolean isSameSet(V x, V y) : 查询样本x和样本y是否属于一个集合;void union(V x, V y) : 把x和y各自所在集合的所有样本合并成一个集合
- isSameSet和union方法的代价越低越好
实现方法
- 每个节点都有一条往上指的指针
- 节点a往上找到的头节点,叫做a所在集合的代表节点
- 查询x和y是否属于同一个集合,就是看看找到的代表节点是不是一个
- 把x和y各自所在集合的所有点合并成一个集合,只需要小集合的代表点挂在大集合的代表点的下方即可
优化
- 节点往上找代表点的过程,把沿途的链变成扁平的
- 小集合挂在大集合的下面
- 如果方法调用很频繁,那么单次调用的代价为O(1),两个方法都如此
public int findCircleNum(int[][] isConnected) {
int N = isConnected.length;
UnionFind uf = new UnionFind(N);
for(int i = 0; i <N - 1; i++) {
for(int j= i + 1; j < N; j++) {
if(isConnected[i][j] == 1) {
uf.union(i,j);
}
}
}
return uf.sets;
}
public class UnionFind {
int sets;
private int[] parent;
private int[] help;
private int[] size;
public UnionFind(int N) {
sets =N;
parent = new int[N];
help = new int[N];
size = new int[N];
for (int i = 0; i < N; i++) {
parent[i] = i;
size[i] = 1;
}
}
private int find(int i) {
int hi = 0;
while(i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for(hi--;hi >= 0; hi--) {
parent[help[hi]] = i;
}
return i;
}
private void union(int i,int j) {
int f1 = find(i);
int f2 = find(j);
if(f1 != f2) {
if(size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
} else {
size[f2] += size[f1];
parent[f1] = f2;
}
sets--;
}
}
}
public static int numIslands3(char[][] board) {
int islands = 0;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] == '1') {
islands++;
infect(board, i, j);
}
}
}
return islands;
}
public static void infect(char[][] board, int i, int j) {
if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] != '1') {
return;
}
board[i][j] = 0;
infect(board, i - 1, j);
infect(board, i + 1, j);
infect(board, i, j - 1);
infect(board, i, j + 1);
}
public static List<Integer> numIslands21(int m, int n, int[][] positions) {
UnionFind1 uf = new UnionFind1(m, n);
List<Integer> ans = new ArrayList<>();
for (int[] position : positions) {
ans.add(uf.connect(position[0], position[1]));
}
return ans;
}
public static class UnionFind1 {
private int[] parent;
private int[] size;
private int[] help;
private final int row;
private final int col;
private int sets;
public UnionFind1(int m, int n) {
row = m;
col = n;
sets = 0;
int len = row * col;
parent = new int[len];
size = new int[len];
help = new int[len];
}
private int index(int r, int c) {
return r * col + c;
}
private int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (hi--; hi >= 0; hi--) {
parent[help[hi]] = i;
}
return i;
}
private void union(int r1, int c1, int r2, int c2) {
if (r1 < 0 || r1 == row || r2 < 0 || r2 == row || c1 < 0 || c1 == col || c2 < 0 || c2 == col) {
return;
}
int i1 = index(r1, c1);
int i2 = index(r2, c2);
if (size[i1] == 0 || size[i2] == 0) {
return;
}
int f1 = find(i1);
int f2 = find(i2);
if (f1 != f2) {
if (size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
} else {
size[f2] += size[f1];
parent[f1] = f2;
}
sets--;
}
}
public int connect(int r, int c) {
int index = index(r, c);
if (size[index] == 0) {
parent[index] = index;
size[index] = 1;
sets++;
union(r - 1, c, r, c);
union(r + 1, c, r, c);
union(r, c - 1, r, c);
union(r, c + 1, r, c);
}
return sets;
}
}