2018-05-01 15:13:08

并查集是一个时空复杂度非常优越的数据结构,并且通过优化后其复杂度为<O(1),O(n)>。

并查集的优化主要有两个方面:

  • 路径压缩
  • 按rank来合并

并查集的优化及应用_i++

路径压缩:

并查集的优化及应用_i++_02

按rank合并:

并查集的优化及应用_i++_03



public class UnionFindSet {
private int[] parent;
private int[] rank;

public UnionFindSet(int n) {
parent = new int[n + 1];
rank = new int[n + 1];
for (int i = 0; i < n + 1; i++) {
parent[i] = i;
rank[i] = 1;
}
}

public int find(int i) {
if (parent[i] != i)
parent[i] = find(parent[i]);
return parent[i];
}

public boolean union(int i, int j) {
int pi = find(i);
int pj = find(j);

if (pi == pj) return false;

if (rank[pi] > rank[pj])
parent[pj] = pi;
else if (rank[pi] < rank[pj])
parent[pi] = pj;
else {
parent[pj] = pi;
rank[pi]++;
}
return true;
}
}


  • 684. Redundant Connection

问题描述:

并查集的优化及应用_并查集_04

问题求解:

树形下的无向图判断环路问题,图的描述方式是采用边集。

并查集本身就是树形结构,而树是一个无向图,具体来说,树是一个无环的连通图,所以本题可以直接使用并查集来进行求解。



    public int[] findRedundantConnection(int[][] edges) {
UnionFindSet ufs = new UnionFindSet(edges.length + 1);
int[] res = new int[2];
for (int[] pair : edges) {
if (!ufs.union(pair[0], pair[1])) {
res[0] = Math.min(pair[0], pair[1]);
res[1] = Math.max(pair[0], pair[1]);
break;
}
}
return res;
}


2019.04.21



    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[] edge : edges) {
if (!union(parent, edge[0], edge[1])) {
Arrays.sort(edge);
return edge;
}
}
return null;
}

private int find(int[] parent, int i) {
if (parent[i] != i) {
parent[i] = find(parent, parent[i]);
}
return parent[i];
}

private boolean union(int[] parent, int i, int j) {
int pi = find(parent, i);
int pj = find(parent, j);
if (pi == pj) return false;
parent[pj] = pi;
return true;
}


  

  • 547. Friend Circles

问题描述:

并查集的优化及应用_问题求解_05

问题求解:



class Solution {
public int findCircleNum(int[][] M) {
UnionFindSet ufs = new UnionFindSet(M.length);
int res = 0;
for (int i = 0; i < M.length; i++) {
for (int j = 0; j < i; j++) {
if (M[i][j] == 1)
ufs.union(i, j);
}
}
for (int i = 0; i < ufs.parent.length; i++)
if (ufs.parent[i] == i) res++;
return res;
}
}

class UnionFindSet {
public int[] parent;
private int[] rank;

public UnionFindSet(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
rank[i] = 1;
}
}

public int find(int i) {
if (parent[i] != i)
parent[i] = find(parent[i]);
return parent[i];
}

public boolean union(int i, int j) {
int pi = find(i);
int pj = find(j);

if (pi == pj) return false;

if (rank[pi] > rank[pj])
parent[pj] = pi;
else if (rank[pi] < rank[pj])
parent[pi] = pj;
else {
parent[pj] = pi;
rank[pi]++;
}
return true;
}
}


 

  • 765. Couples Holding Hands

问题描述:

并查集的优化及应用_数组_06

问题求解:

这里有一个O(n)的做法, 一次考虑两个凳子,假设他们不为夫妇,为了让这两个位置坐的恰好是一对夫妇,那么我们就需要调整其中一个人的位置,如此调整直到所有的夫妇相邻,交换的次数就是答案。下面给出证明。

将给定的row抽象成一个n个顶点的无向图(可能包含重边),例如:

(_ _) (_ _) ... (_ _)
(v1 ) (v2 ) ... (vn )


vi和vj之间存在边当且仅当vi和vj中存在一对夫妇,例如vi = (0,2),vj = (1, 4)存在一对夫妇(0, 1),而vi = (0, 2),vj = (1, 3)之间则存在两对夫妇(0, 1)(2, 3),此vi和vj存在重边。

考虑row数组形成的无向图,可以肯定要么是孤立的单个节点,要么是多个孤立的圈,例如row = [0, 1]是一个孤立的点、row = [0, 2, 1, 3]则包含一个圈v1, v2、row = [0, 3, 4, 1, 2, 5, 6, 8, 7, 9],包含两个孤立的圈v1, v2, v3和v4, v5。

对于一个圈来说,假设他有n个节点,那么至少需要n-1次交换即可让每个夫妇相邻。有了这个结论,假定row数组有n个点,m个孤立的圈,那么至少需要n-m次交换即可。



    public int minSwapsCouples(int[] row) {
int n = row.length / 2;
int[] parent = new int[n];
int cnt = n;
for (int i = 0; i < n; i++) parent[i] = i;
for (int i = 0; i < n * 2; i += 2) {
if (union(parent, row[i] / 2, row[i + 1] / 2)) cnt--;
}
return n - cnt;
}

private int find(int[] parent, int i) {
if (parent[i] != i) {
parent[i] = find(parent, parent[i]);
}
return parent[i];
}

private boolean union(int[] parent, int i, int j) {
int pi = find(parent, i);
int pj = find(parent, j);
if (pi == pj) return false;
parent[pj] = pi;
return true;
}


 

  • 947. Most Stones Removed with Same Row or Column

问题描述:

并查集的优化及应用_无向图_07

问题求解:

这个问题可以转化为求图中连通数的问题,也就是经典的陆地数量问题。对于x | y相等的两个石头我们需要建立他们之间的联系。

经典的连通子树问题可以使用dfs进行求解,从dfs的算法过程我们可以看到其实是一棵以起始点为root的树,因此,在这次dfs中我们总可以从叶子节点开始选取,知道最后只剩下root节点。

最终的答案就是所有的stones的数目 - 连通块的数目。

这里并不打算使用dfs来进行求解,将使用ufs来进行求解。

使用并查集并不需要那么形式化的专门使用一个类来进行表征,这就是一个简单的数据结构,只需要在使用前进行定义就好了,另外,由于parent的数目范围不确定,所以在很多时候使用数组并不是一个合适的选择,使用hash表更能方便我们解决问题,本题就是使用hash表来进行的并查集的实现。



    public int removeStones(int[][] stones) {
Map<Integer, Integer> parent = new HashMap<>();
int n = stones.length;
for (int[] stone : stones) {
union(parent, stone[0], stone[1] + 10000);
}
int cnt = 0;
for (int key : parent.keySet()) {
if (parent.get(key) == key) cnt++;
}
return n - cnt;
}

private int find(Map<Integer, Integer> parent, int i) {
if (!parent.containsKey(i)) parent.put(i, i);
if (parent.get(i) != i) {
parent.put(i, find(parent, parent.get(i)));
}
return parent.get(i);
}

private boolean union(Map<Integer, Integer> parent, int i, int j) {
int pi = find(parent, i);
int pj = find(parent, j);
if (pi == pj) return false;
parent.put(pj, pi);
return true;
}


 

  • 959. Regions Cut By Slashes

问题描述:

并查集的优化及应用_并查集_08

问题求解:

主要问题就是怎么将字符串的输入进行转化,这里采用的转化方式是将每个cell看成4个区域,0-3。根据不同的情况可以将各个区域进行合并,这样本题就又变成了并查集问题。



    int n;

public int regionsBySlashes(String[] grid) {
n = grid.length;
int[] parent = new int[n * n * 4];
for (int i = 0; i < parent.length; i++) parent[i] = i;
int size = n * n * 4;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i > 0 && union(parent, getIdx(i - 1, j, 2), getIdx(i, j, 0))) size--;
if (j > 0 && union(parent, getIdx(i, j - 1, 1), getIdx(i, j, 3))) size--;
if (grid[i].charAt(j) != '/') {
if (union(parent, getIdx(i, j, 0), getIdx(i, j, 1))) size--;
if (union(parent, getIdx(i, j, 2), getIdx(i, j, 3))) size--;
}
if (grid[i].charAt(j) != '\\') {
if (union(parent, getIdx(i, j, 0), getIdx(i, j, 3))) size--;
if (union(parent, getIdx(i, j, 1), getIdx(i, j, 2))) size--;
}
}
}
return size;
}

private int find(int[] parent, int i) {
if (parent[i] != i) {
parent[i] = find(parent, parent[i]);
}
return parent[i];
}

private boolean union(int[] parent, int i, int j) {
int pi = find(parent, i);
int pj = find(parent, j);
if (pi == pj) return false;
parent[pj] = pi;
return true;
}

private int getIdx(int i, int j, int num) {
return i * n * 4 + j * 4 + num;
}