并查集

朋友圈问题

社交网络:判断两个人是否属于同一个朋友圈

朋友圈问题:已知,有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 形成第三个朋友圈。
  • 04 之间的连接,使得第一个朋友圈和第二个朋友圈合并。

下面是代码示例

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;  // 返回朋友圈的数量
    }
}

关键点

  • 路径压缩与秩:结合路径压缩和秩优化,能够使并查集的查找和合并操作接近常数时间复杂度。
  • 避免不平衡:通过维护秩,可以有效避免形成深树,提升整体性能。

这种优化在处理大量数据时尤其重要,使得并查集在实际应用中更加高效。

posted @ 2025-08-13 21:29  vonlinee  阅读(15)  评论(0)    收藏  举报