CS61B – Lab 05 UnionFind

Lab 05 要求我们实现一个带路径压缩的并查集(Union-Find with Path Compression)的数据结构,带路径压缩的并查集是一种高效的数据结构,用于管理不相交集合的合并与查询操作。它通过路径压缩技术优化了查找操作的性能。

不过预传统意义上的并查集不同的是, CS61B 中进行判断的依据是并查集的整体大小而非常规意义上的深度(也就是我们所称的秩, Rank)

其主要成员包括:

  • Parent数组:记录每个元素的父节点
  • Rank数组:记录每个集合的秩(树的高度),用于按秩合并优化
  • Size数组:记录每个集合的大小,此处用于合并

路径压缩

在查找根节点的过程中,我们可以将查找路径上的所有节点直接连接到根节点,从而扁平化树结构,减少后续查找操作的深度。

核心代码是

parent[x] = find(parent[x]) 

应用场景

  1. 连通性判断
    • 网络连接检测
    • 社交网络好友关系
  2. 图算法
    • Kruskal最小生成树算法
    • 检测图中环的存在
  3. 图像处理
    • 连通组件标记
  4. 动态连通性问题
    • 实时判断两个元素是否连通

算法优势

  1. 高效性:路径压缩使得后续操作极其快速
  2. 简单性:实现代码简洁易懂
  3. 实用性:广泛应用于各种算法和系统中

注意事项

  • 路径压缩会改变树的结构,但不影响连通性的正确性
  • 按秩合并是可选的,但能进一步优化性能
  • 在实际应用中,通常同时使用路径压缩和按秩合并

实现(Size 版本)

public class UnionFind {
    // TODO: Instance variables
    final int N;
    int [] parent;  // 父节点数组
    int [] rank;    // Rank
    int [] size;    // 大小
    /* Creates a UnionFind data structure holding N items. Initially, all
       items are in disjoint sets. */
    public UnionFind(int N) {
        // 初始化方法
        this.N = N;
        this.parent = new int[N];
        this.rank = new int[N];
        this.size = new int[N];
        for (int i = 0; i < N; i++) {
            parent[i] = i;
            rank[i] = 0;
            size[i] = 1;
        }
    }

    /* Returns the size of the set V belongs to. */
    public int sizeOf(int v) {
        if (v > N || v < 0){
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        // 返回根节点的 Size
        return size[find(v)];
    }

    /* Returns the parent of V. If V is the root of a tree, returns the
       negative size of the tree for which V is the root. */
    public int parent(int v) {
        if (v > N || v < 0){
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        int root = find(v);
        return root == v ? -size[root] : parent[v];
    }

    /* Returns true if nodes/vertices V1 and V2 are connected. */
    public boolean connected(int v1, int v2) {
        if (v1 > N || v2 > N || v1 < 0 || v2 < 0) {
            throw new IllegalArgumentException("Index out of bounds: " + v1 + " or " + v2);
        }
        return find(v1) == find(v2);
    }

    /* Returns the root of the set V belongs to. Path-compression is employed
       allowing for fast search-time. If invalid items are passed into this
       function, throw an IllegalArgumentException. */
    public int find(int v) {
        if (v > N || v < 0){
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        if(this.parent[v] == v) return v;
        return this.parent[v] = find(this.parent[v]);  // 路径压缩
    }

    /* Connects two items V1 and V2 together by connecting their respective
       sets. V1 and V2 can be any element, and a union-by-size heuristic is
       used. If the sizes of the sets are equal, tie break by connecting V1's
       root to V2's root. Union-ing an item with itself or items that are
       already connected should not change the structure. */
    public void union(int v1, int v2) {
        if (v1 > N || v2 > N || v1 < 0 || v2 < 0) {
            throw new IllegalArgumentException("Index out of bounds: " + v1 + " or " + v2);
        }
        int root1 = find(v1);
        int root2 = find(v2);
        if (root1 == root2) {
            return;
        }

        if (size[root1] < size[root2]) {
            parent[root1] = root2;
            size[root2] += size[root1];
        } else if (size[root1] > size[root2]) {
            parent[root2] = root1;
            size[root1] += size[root2];
        } else  {
            parent[root1] = root2;
            ++rank[root2];
            size[root2] += size[root1];
        }
    }

}

实现(Rank 版本)

public class UnionFind {
    private final int N;
    private final int[] parent;
    private final int[] rank;
    private final int[] size;
    
    /* Creates a UnionFind data structure holding N items. Initially, all
       items are in disjoint sets. */
    public UnionFind(int N) {
        this.N = N;
        this.parent = new int[N];
        this.rank = new int[N];
        this.size = new int[N];
        for (int i = 0; i < N; i++) {
            parent[i] = i;
            rank[i] = 0;
            size[i] = 1;
        }
    }

    /* Returns the size of the set V belongs to. */
    public int sizeOf(int v) {
        if (v < 0 || v >= N) {
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        return size[find(v)];
    }

    /* Returns the parent of V. If V is the root of a tree, returns the
       negative size of the tree for which V is the root. */
    public int parent(int v) {
        if (v < 0 || v >= N) {
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        // 直接返回父节点,不进行路径压缩
        return parent[v];
    }

    /* Returns true if nodes/vertices V1 and V2 are connected. */
    public boolean connected(int v1, int v2) {
        if (v1 < 0 || v1 >= N || v2 < 0 || v2 >= N) {
            throw new IllegalArgumentException("Index out of bounds");
        }
        return find(v1) == find(v2);
    }

    /* Returns the root of the set V belongs to. Path-compression is employed
       allowing for fast search-time. If invalid items are passed into this
       function, throw an IllegalArgumentException. */
    public int find(int v) {
        if (v < 0 || v >= N) {
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        if (parent[v] != v) {
            parent[v] = find(parent[v]);  // 递归路径压缩
        }
        return parent[v];
    }

    /* 迭代版本的find方法,避免递归深度问题 */
    public int findIterative(int v) {
        if (v < 0 || v >= N) {
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        
        // 找到根节点
        int root = v;
        while (root != parent[root]) {
            root = parent[root];
        }
        
        // 路径压缩
        while (v != root) {
            int next = parent[v];
            parent[v] = root;
            v = next;
        }
        
        return root;
    }

    /* Connects two items V1 and V2 together using union-by-rank heuristic. */
    public void union(int v1, int v2) {
        if (v1 < 0 || v1 >= N || v2 < 0 || v2 >= N) {
            throw new IllegalArgumentException("Index out of bounds");
        }
        
        int root1 = find(v1);
        int root2 = find(v2);
        
        if (root1 == root2) {
            return;  // 已在同一集合中
        }

        // 按秩合并:将秩较小的树合并到秩较大的树下
        if (rank[root1] < rank[root2]) {
            parent[root1] = root2;
            size[root2] += size[root1];
        } else if (rank[root1] > rank[root2]) {
            parent[root2] = root1;
            size[root1] += size[root2];
        } else {
            // 秩相等时,任选一个作为根,并增加秩
            parent[root1] = root2;
            rank[root2]++;
            size[root2] += size[root1];
        }
    }

    /* 获取元素的秩(主要用于调试) */
    public int getRank(int v) {
        if (v < 0 || v >= N) {
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        return rank[find(v)];
    }

    /* 检查元素是否为根节点 */
    public boolean isRoot(int v) {
        if (v < 0 || v >= N) {
            throw new IllegalArgumentException("Index out of bounds: " + v);
        }
        return parent[v] == v;
    }

    /* 获取集合数量 */
    public int countSets() {
        int count = 0;
        for (int i = 0; i < N; i++) {
            if (parent[i] == i) {
                count++;
            }
        }
        return count;
    }
}

以上


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注