并查集 (Union-Find)算法

本文详细介绍了并查集(Union-Find)算法,用于解决动态连通性问题。并查集通过Union和Find操作实现对象的连接与查询。在优化方面,采用了路径压缩(包括隔代压缩和彻底压缩)提高查找效率,并使用辅助的size数组在union操作中避免树退化,保持平衡。此外,还提供了完整的Java实现代码。
摘要由CSDN通过智能技术生成

并查集 (Union-Find)

问题现象

并查集算法主要是用来解决动态连通性问题

动态连通性(Dynamic Connectivity)

根据《Algorithms4》可知,动态连通性即给定N个对象的集合,可以做以下操作:

  • Union操作:连接两个对象
  • Find/connected查询: 两个对象是否连接?

其中,连通具有以下性质:

  • Reflexive: p和p是相连的,又称自反性;
  • Symmetric: 如果p和q是相连的,那么q和p也是相连的,又称对称性;
  • Transitive: 如果p和q相连且q和r相连,那么p和r相连,又称传递性。

问题建模

API

public class UnionFind {
    int[] parent; // 存储某个对象的父节点,大小为N,初始化为对应的数组下标,代表独立分量,且是树根,也即初始化为parent[i] = i;

    UnionFind(int N) { // 构造函数
    }

    public void union(int p, int q) { // 连通 q, p分量
    }

    private int find(int x) { // 获取x的根节点
    }

    public boolean connected(int p, int q) { // 判断q, p是否连通
    }

    public int count() { // 获取连通分量个数
    }
}

find方法

    private int find(int x) { // 获取x的根节点
        while (x != parent[x]) { // 根节点的数组值就是自身的下标,也就是parent[x] = x
            x = parent[x];
        }
        return x;
    }

union方法

    public void union(int p, int q) { // 连通 q, p分量
        int rootQ = find(q);
        int rootP = find(p);
        if (rootP == rootQ) { // 若根节点相同,则说明两个分量已相连,直接返回
            return;
        }
        parent[rootQ] = rootP; // 将Q的根节点连接到P的根节点上,也可以parent[rootP] = rootQ
    }

优化

从上述代码可知,union方法是直接将某棵树的根节点连接在另一棵树上,在多次执行此操作后,树可能会很高,甚至退化成链表。而find方法是从某一个节点往上依次遍历到根节点,若树的高度很高,则会导致查找效率低下。

find方法优化
  • 隔代压缩:把路径上的每个节点的父节点指向其祖父节点
    private int find(int x) { // 获取x的根节点
        while (x != parent[x]) { // 根节点的数组值就是自身的下标,也就是parent[x] = x
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }
  • 彻底压缩:先找到当前结点的根结点,然后把沿途上所有的节点都指向根节点
    private int find(int x) { // 获取x的根节点
        if (x != parent[x]) { // 根节点的数组值就是自身的下标,也就是parent[x] = x
            parent[x] = find[parent[x]];
        }
        return x;
    }
union方法优化

增加一个数组,大小为N,初始化为对应的数组下标,也即初始化为size[i] = i,记录每棵树的高度,在查找的时候比较树高,高度小的树合并到高度搞的树上,相等的话两者均可作为根节点,并把高度加一;

    public void union(int p, int q) { // 连通 q, p分量
        int rootQ = find(q);
        int rootP = find(p);
        if (rootP == rootQ) { // 若根节点相同,则说明两个分量已相连,直接返回
            return;
        }
        if (size[rootP] > size[rootQ]) {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        } else {
            parent[rootP] = rootQ;
            size[rootQ] = size[rootP];
        }
        count--;
    }

完整算法

public class UnionFind {
    int count;
    int[] parent;
    int[] size;

    UnionFind(int N) {
        this.count = N;
        this.parent = new int[N];
        this.size = new int[N];
        for (int i = 0; i < N; ++i) {
            parent[i] = i;
            size[i] = 1;
        }
    }

    public void union(int p, int q) { // 连通 q, p分量
        int rootQ = find(q);
        int rootP = find(p);
        if (rootP == rootQ) {
            return;
        }
        if (size[rootP] > size[rootQ]) {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
        } else {
            parent[rootP] = rootQ;
            size[rootQ] = size[rootP];
        }
        count--;
    }

    private int find(int x) { // 获取x的父节点
        while (x != parent[x]) {
            parent[x] = parent[parent[x]];
            x = parent[x];
        }
        return x;
    }

    public boolean connected(int p, int q) { // 判断q p是否连通
        int rootQ = find(q);
        int rootP = find(p);
        return rootP == rootQ;
    }

    public int count() { // 获取联通分量个数
        return count;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值