在一些应用的问题中,需将n个不同的元素划分成一组不相交的集合。开始时,每个元素自成一格单元素集合,然后按一定顺序将属于同一组的元素的集合合并。其间要反复用到查询某个元素属于哪个集合的运算。适合于描述这类问题的抽象数据类型称为并查集。
并查集
并查集(Disjoint-set Union)是一种常见的数据结构,用于维护一组不相交的集合,支持合并两个集合和查询某个元素所属的集合。并查集常用于解决图论、连通性问题和动态连通性问题等,具有较高的效率和易用性。
并查集的基本操作包括:
- 初始化:将每个元素初始化为一个独立的集合,即每个元素都是该集合的根节点
 - 合并:将两个不相交的集合合并成一个集合,即将其中一个集合的根节点连接到另一个集合的根节点上
 - 查找:查找某个元素所属的集合,即找到该元素所在集合的根节点
 
并查集的优点是实现简单,可以在近乎O(1)的时间复杂度内完成合并和查找操作,因此常用于需要频繁合并和查询集合的问题中。

并查集的实现
以下是使用Java语言实现并查集的示例代码:
class UnionFind {
    private int[] parent;
    private int[] rank;
    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
	/**
	 * 合并两个集合,使用平衡再优化
	 */
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            rank[rootX] += rank[rootY];
        } else {
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }
	/**
	 * 判断两个节点是否在同一个集合中
	 */
    public boolean connected(int x, int y) {
        return find(x) == find(y);
    }
}
 
在这个示例代码中,我们使用两个数组
parent和rank来表示并查集。
- parent[i]表示节点i的父节点,初始时每个节点的父节点都是它自己
 - rank[i]表示以节点i为根节点的子树的深度,初始时每个节点的深度都为0
 
find()方法用于查找某个节点所在的集合,它采用路径压缩的方式来优化查找效率。在查找节点x所在的集合时,如果节点x不是根节点,就将它的父节点设置为根节点,这样下次查找时就可以直接找到- 根节点,从而加速查找效率。union()方法用于合并两个集合,它采用按秩合并(rank)的方式来优化合并效率。在合并两个集合时,先找到它们的根节点,如果两个根节点相同,则说明它们已经在同一个集合中,直接返回;否则,- 将深度较小的根节点连接到深度较大的根节点下面。connected()方法用于判断两个节点是否在同一个集合中,它直接调用find()方法来查找它们所在的集合,并比较两个集合的根节点是否相同
通过这样的实现,我们可以在近乎O(1)的时间复杂度内完成并查集的合并和查找操作。
Leetcode 真题
最长连续序列
解题思路:构造并查集,获取集合中节点的最大深度
class Solution {
    public int longestConsecutive(int[] nums) {
        Map<Integer, Integer> map = new HashMap<>();
        UnionFind uf = new UnionFind(nums.length);
        for (int i = 0; i < nums.length; i++) {
            // 存在重复元素,跳过
            if (map.containsKey(nums[i])){
                continue;
            }
            if (map.containsKey(nums[i] - 1)) {
                uf.union(i, map.get(nums[i] - 1));
            }
            if (map.containsKey(nums[i] + 1)) {
                uf.union(i, map.get(nums[i] + 1));
            }
            map.put(nums[i], i);
        }
        return uf.getMaxConnectSize();
    }
}
class UnionFind {
    private int[] parent;
    private int[] rank;
    public UnionFind(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 x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            rank[rootX] += rank[rootY];
        } else {
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }
    public int getMaxConnectSize() {
        int maxSize = 0;
        for (int i = 0; i < parent.length; i++) {
            if (i == parent[i]) {
                maxSize = Math.max(maxSize, rank[i]);
            }
        }
        return maxSize;
    }
}
 
省份数量
解题思路:构造并查集,获取集合中父节点为自身的节点(独立区域)
class Solution {
    public int findCircleNum(int[][] isConnected) {
        UnionFind uf = new UnionFind(isConnected.length);
        for (int i = 0; i < isConnected.length; i++) {
            for (int j = i + 1; j < isConnected[i].length; j++) {
                if (isConnected[i][j] == 1) {
                    uf.union(i, j);
                }
            }
        }
        int count = 0;
        for (int i = 0; i < isConnected.length; i++) {
            if (uf.parent[i] == i) {
                count++;
            }
        }
        return count;
    }
}
class UnionFind {
    public int[] parent;
    public int[] rank;
    public UnionFind(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 x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }
        if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            rank[rootX] += rank[rootY];
        } else {
            parent[rootX] = rootY;
            rank[rootY] += rank[rootX];
        }
    }
}
 
冗余连接
解题思路:
遍历每一条边,对于每一条边的两个端点:
- 如果它们已经在同一个集合中,说明在加入这条边之后会出现环,此时这条边就是需要删除的边
 - 否则将这两个端点加入同一个集合中
 
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;
	}
	int[] result = null;
	for (int[] edge : edges) {
		int u = edge[0], v = edge[1];
		int pu = find(parent, u), pv = find(parent, v);
		if (pu == pv) {
			// u和v已经在同一个集合中,说明加入这条边会出现环
			result = edge;
		} else {
			// 将u和v加入同一个集合中
			parent[pu] = pv;
		}
	}
	return result;
}
/**
 * 查找节点x所在集合的根节点(路径压缩)
 */
private int find(int[] parent, int x) {
	while (parent[x] != x) {
		parent[x] = parent[parent[x]];
		x = parent[x];
	}
	return x;
}
 
参考资料:
- 快速并查集(Java实现)
 - 并查集
 














![C嘎嘎~~[类 中篇]](https://img-blog.csdnimg.cn/4820ec7769e145649ebe3453a22b14d8.png)




