并查集Disjoint Set Union

目录

数据结构

方法实现

优化技巧


实现一个基于哈希表的并查集(Disjoint Set Union, DSU)数据结构,使用了路径压缩和按秩合并的优化技巧。下面我将详细解释这个实现的原理和各个部分的功能。

public interface IDisjointSet {
    void add(E x);
    void union(E x, E y);

    boolean isConnected(E x, E y);

    int getSize(E x);

    E find(E x);
}

数据结构

1. items:一个`Map`,用于存储每个元素的父节点。对于每个元素`x`,`items.get(x)`表示`x`的父节点。初始时,每个元素都是自己的父节点,即`items.put(x, x)`。

2. count:一个`Map`,用于存储每个集合的大小。对于每个集合的根节点`root`,`count.get(root)`表示该集合包含的元素个数。初始时,每个元素自成一个集合,大小为1,即`count.put(x, 1)`。

3. rank:一个`Map`,用于存储每个集合的秩(rank),秩在这里表示树的高度,用于按秩合并优化。初始时,每个元素自成一棵树,高度为0,即`rank.put(x, 0)`。

方法实现

1. add(E e):
   - 如果元素`e`不存在于`items`中,则将其添加进去,并设置其父节点为自身,集合大小为1,秩为0。

2. find(E x):
   - 找到元素`x`所属集合的根节点,并进行路径压缩。
   - 路径压缩:在查找根节点的过程中,将经过的节点直接连接到根节点,减少树的高度,加快后续查找速度。
   - 具体步骤:
     - 循环找到根节点`root`,即`items.get(root) == root`。
     - 然后进行路径压缩,将从`x`到`root`路径上的所有节点直接连接到`root`。

3. union(E x, E y):
   - 将元素`x`和`y`所在的集合合并。
   - 首先找到`x`和`y`的根节点`rootX`和`rootY`。
   - 如果`rootX`和`rootY`不同,则进行合并:
     - 按秩合并:比较两个集合的秩,将秩较小的集合合并到秩较大的集合上。
     - 如果秩相等,可以任意选择一个集合合并到另一个上,并将合并后的集合秩加1。

4. getSize(E x):
   - 返回元素`x`所在集合的大小,通过找到`x`的根节点,然后从`count`中获取集合大小。

5. isConnected(E x, E y):
   - 判断元素`x`和`y`是否在同一个集合中,通过比较它们的根节点是否相同。

优化技巧

- 路径压缩:在`find`操作中,通过将路径上的所有节点直接连接到根节点,大大减少了树的高度,使得后续的`find`操作非常快,几乎为常数时间。

- 按秩合并:在`union`操作中,通过维护每个集合的秩,并在合并时将秩较小的集合合并到秩较大的集合上,保证了树的高度尽可能小,从而保持了`find`操作的高效性。

这个并查集实现使用了哈希表来存储元素及其父节点、集合大小和秩,适用于元素数量不确定或者元素是对象的场景。通过路径压缩和按秩合并,保证了高效的查询和合并操作,时间复杂度接近O(1)。

完整代码:

public class MyDisjointSet implements IDisjointSet {
    private Map items;  // 存储每个元素的父节点
    private Map count;  // 存储每个集合的大小
    private Map rank;   // 排序,即树的高度(用于优化合并操作)

    // 构造函数
    public MyDisjointSet() {
        items = new HashMap<>();
        count = new HashMap<>();
        rank = new HashMap<>();
    }

    // 添加元素
    public void add(E e) {
        if (!items.containsKey(e)) {
            items.put(e, e);
            count.put(e, 1);
            rank.put(e, 0);
        }
    }

    // 查找元素 x 所属的集合,并进行路径压缩
    @Override
    public E find(E x) {
        E root = x;
        while (items.get(root) != root) {
            root = items.get(root);
        }
        // 路径压缩
        E current = x;
        while (current != root) {
            E parent = items.get(current);
            items.put(current, root);
            current = parent;
        }
        return root;
    }

    // 合并元素 x 和 y 所在的集合
    @Override
    public void union(E x, E y) {
        E rootX = find(x);
        E rootY = find(y);
        if (!rootX.equals(rootY)) {
            // 按秩合并
            int rankX = rank.get(rootX);
            int rankY = rank.get(rootY);
            if (rankX > rankY) {
                items.put(rootY, rootX);
                count.put(rootX, count.get(rootX) + count.get(rootY));
            } else {
                items.put(rootX, rootY);
                count.put(rootY, count.get(rootY) + count.get(rootX));
                if (rankX == rankY) {
                    rank.put(rootY, rankY + 1);
                }
            }
        }
    }

    // 获取元素 x 所属集合的大小
    @Override
    public int getSize(E x) {
        E root = find(x);
        return count.get(root);
    }

    // 判断元素 x 和 y 是否属于同一个集合
    @Override
    public boolean isConnected(E x, E y) {
        return find(x).equals(find(y));
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        IDisjointSet disjointSet = new MyDisjointSet<>();
        disjointSet.add(1);
        disjointSet.add(2);
        disjointSet.add(3);
        disjointSet.add(4);
        disjointSet.add(5);
        disjointSet.add(6);
        disjointSet.add(7);
        disjointSet.add(8);
        disjointSet.add(9);
        disjointSet.add(10);
        disjointSet.union(1, 2);
        disjointSet.union(2, 3);
        disjointSet.union(4, 5);
        disjointSet.union(6, 7);
        disjointSet.union(5, 6);
        disjointSet.union(3, 7);
        System.out.println("connect:");
        System.out.println(disjointSet.isConnected(1, 3));
        System.out.println(disjointSet.isConnected(1, 5));
        System.out.println(disjointSet.isConnected(4, 6));
        System.out.println(disjointSet.isConnected(4, 7));
        System.out.println(disjointSet.isConnected(1, 7));
        System.out.println("size:");
        System.out.println(disjointSet.getSize(1));
        System.out.println(disjointSet.getSize(2));
        System.out.println(disjointSet.getSize(3));
        System.out.println(disjointSet.getSize(4));
        System.out.println(disjointSet.getSize(5));
        System.out.println(disjointSet.getSize(6));
        System.out.println(disjointSet.getSize(7));
        System.out.println("find:");
        System.out.println(disjointSet.find(1));
        System.out.println(disjointSet.find(2));
        System.out.println(disjointSet.find(3));
        System.out.println(disjointSet.find(4));
        System.out.println(disjointSet.find(5));
        System.out.println(disjointSet.find(6));
        System.out.println(disjointSet.find(7));
        System.out.println(disjointSet.find(8));
        System.out.println(disjointSet.find(9));
    }
}

结果:

connect:
true
true
true
true
true
size:
7
7
7
7
7
7
7
find:
7
7
7
7
7
7
7
8
9

你可能感兴趣的:(Java数据结构,java,数据结构)