并查集
朋友圈问题
社交网络:判断两个人是否属于同一个朋友圈
朋友圈问题:已知,有n个人和m对好友关系,如果两个人是直接的或者间接的好友,那么他们属于一个集合,就在一个朋友圈。求这 n 个人中一共有多少个朋友圈。
举个例子,假设有10个人,好友关系如下:
{0, 1}, {1, 2}, {2, 3}, {4, 5}, {5, 6}, {6, 7}, {0, 4}, {8, 9}
好友关系如下:有三个独立的朋友圈。
0,1,2,3形成一个朋友圈。4,5,6,7形成另一个朋友圈。8,9形成第三个朋友圈。0和4之间的连接,使得第一个朋友圈和第二个朋友圈合并。
下面是代码示例
import java.util.*;
public class FriendCircles {
public static void main(String[] args) {
int n = 10;
// 好友关系
int[][] relations = { {0, 1}, {1, 2}, {2, 3}, {4, 5}, {5, 6}, {6, 7}, {0, 4}, {8, 9} };
System.out.println(findCircleNum(n, relations)); // 输出:3
}
public static int findCircleNum(int n, int[][] relations) {
// 创建邻接表表示好友关系
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
// 填充邻接表
for (int[] relation : relations) {
graph.get(relation[0]).add(relation[1]);
graph.get(relation[1]).add(relation[0]);
}
boolean[] visited = new boolean[n];
int circleCount = 0;
// 遍历每个人,查找朋友圈
for (int person = 0; person < n; person++) {
if (!visited[person]) {
dfs(person, graph, visited);
circleCount++;
}
}
return circleCount;
}
// 深度优先搜索
private static void dfs(int person, List<List<Integer>> graph, boolean[] visited) {
visited[person] = true;
for (int friend : graph.get(person)) {
if (!visited[friend]) {
dfs(friend, graph, visited);
}
}
}
}
并查集
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。下面是使用并查集解决前面的朋友圈问题
import java.util.*;
public class FriendCircles {
public static void main(String[] args) {
int n = 10;
// 好友关系
int[][] relations = { {0, 1}, {1, 2}, {2, 3}, {4, 5}, {5, 6}, {6, 7}, {0, 4}, {8, 9} };
System.out.println(findCircleNum(n, relations)); // 输出: 3
}
public static int findCircleNum(int n, int[][] relations) {
UnionFind unionFind = new UnionFind(n);
// 进行并查集的合并操作
for (int[] relation : relations) {
unionFind.union(relation[0], relation[1]);
}
// 计算不同的朋友圈数量
return unionFind.getCircleCount();
}
}
class UnionFind {
// 用于跟踪每个人的父节点
private int[] parent;
// 朋友圈的数量
private int count;
public UnionFind(int size) {
parent = new int[size];
count = size;
for (int i = 0; i < size; 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) {
parent[rootY] = rootX; // 合并两个集合
count--; // 每合并一个朋友圈,计数减少
}
}
public int getCircleCount() {
return count; // 返回朋友圈的数量
}
}
并查集的秩
秩是并查集的一个优化措施,用于提高合并操作的效率。主要目的是在合并两个集合时,尽量避免形成较深的树,从而提升查找操作的效率。
秩通常表示树的高度或深度。在并查集中,树的高度是指从某个节点到其最远叶子节点的最长路径的长度。通过维护秩,可以在合并时选择将树较小的根合并到树较大的根下,保持树的平衡。
- 合并操作:在合并两个集合时,比较两个根节点的秩:如果一个根的秩大于另一个根的秩,则将较小的根指向较大的根。如果两个根的秩相等,则可以任选一个作为新的根,并将其秩加一。
class UnionFind {
private int[] parent;
private int[] rank; // 秩数组
private int count;
public UnionFind(int size) {
parent = new int[size];
rank = new int[size]; // 初始化秩为0
count = size;
for (int i = 0; i < size; i++) {
parent[i] = i; // 每个人的父节点是自己
rank[i] = 0; // 初始秩为0
}
}
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) {
// 合并秩较小的树到秩较大的树
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX]++; // 只有当两个树的秩相等时,增加新根的秩
}
count--; // 每合并一个朋友圈,计数减少
}
}
public int getCircleCount() {
return count; // 返回朋友圈的数量
}
}
关键点
- 路径压缩与秩:结合路径压缩和秩优化,能够使并查集的查找和合并操作接近常数时间复杂度。
- 避免不平衡:通过维护秩,可以有效避免形成深树,提升整体性能。
这种优化在处理大量数据时尤其重要,使得并查集在实际应用中更加高效。

浙公网安备 33010602011771号