并查集基本原理

并查集

1. 概念

并查集主要是为了解决图论中的动态连通性的问题。现在主要的Union-Find算法主要实现两个API:

class UF{
public:
    void union(int p, int q);
    bool connected(int p, int q);
    int count();
}

这里所说的「连通」是一种等价关系,也就是说具有如下三个性质:

1、自反性:节点 pp 是连通的。

2、对称性:如果节点 pq 连通,那么 qp 也连通。

3、传递性:如果节点 pq 连通,qr 连通,那么 pr 也连通。

2.基本思路

首先就是初始化,并查集需要用到一个数组。

class UF{
private:
    //用来记录连通分量
    int count;
    //用来记录每个节点的父节点
    vector<int> parent;
    //用于平衡性优化,避免头重脚轻
    vector<int> sizes;
public:
    UF(int n) {
        this->count = n;
        parent.resize(n);
        //sizes.resize(n);
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            //sizes[i] = 1;
        }
    }
    
    //连通节点p和节点q
    void union(int p, int q) {
        int rootP = find(p);
        int rootQ = find(q);
        if (rootP == rootQ) return;
        
        parent[rootQ] = rootP;
        count--;
    }
    
    //判断两个节点是否联通
    bool connected(int p, int q) {
        int rootp = find(p);
        int rootq = find(q);
        return rootp == rootq;
    }
    
    //查找x节点的父节点,并进行路径压缩
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    
    //获取连通分量
    int getCount() {
        return this->count;
    }
}

Union-Find 算法的复杂度可以这样分析:构造函数初始化数据结构需要 O(N) 的时间和空间复杂度;连通两个节点 union、判断两个节点的连通性 connected、计算连通分量 count 所需的时间复杂度均为 O(1)。

到这里,相信你已经掌握了 Union-Find 算法的核心逻辑,总结一下我们优化算法的过程:

1、用 parent 数组记录每个节点的父节点,相当于指向父节点的指针,所以 parent 数组内实际存储着一个森林(若干棵多叉树)。

2、用 size 数组记录着每棵树的重量,目的是让 union 后树依然拥有平衡性,保证各个 API 时间复杂度为 O(logN),而不会退化成链表影响操作效率。

3、在 find 函数中进行路径压缩,保证任意树的高度保持在常数,使得各个 API 时间复杂度为 O(1)。使用了路径压缩之后,可以不使用 size 数组的平衡优化。

posted @ 2022-05-25 14:13  IU_UI  阅读(63)  评论(0编辑  收藏  举报