Jason Zhou的Coding之路 凡是过往皆为序章

Union Find 查并集


并查集(Union-Find)是集合元素根据相交与否进行划分的数据结构。本文简单介绍原理,C++ 代码实现和少量案例。

基本逻辑

  • 等价关系:定义在集合 S 上的元素,描述两元素之间的关系 R,且此关系具有自反性、对称性、传递性,称 R 为等价关系。
  • 等价关系的两种常见存放方式:
    • 布尔矩阵:集合 S 有 N 个元素,则利用 $N \times N$ 存放这种所有关系,真则相关,假则无关。
    • 等价类(不相交集):$x \in S$ 的等价类包含所有与 x 相关的元素。这样任意两个等价类不相交。所有等价类的并集为整个集合。
  • 不相交集合主要实现两个操作,find 和 union。
    • find:查找某个元素属于哪个子集,返回包含给定元素的子集的名称。
    • union:添加等价关系。涉及到判断对应的不相交集合是否存在,不相交集合的是否相同,合并不相交集合。

实现思路

  • 采用树的结构进行存储,每个结点只需要记录父节点即可,双亲表示法。所以可以利用整数数组来存储。由于可能有多个不相交集合,所以整个数组是一颗树。
  • find 和 union 性能受到形成的树的高度所影响。
  • 改进措施:
    • union 按照高度、规模并。
    • find 路径压缩
    • 其中路径压缩和按照规模并是兼容的,两者可以同时使用。
class DisjointSet{
private:
    int size;
    int *parent;

public:
    DisjoinSet(int s);
    ~DisjointSet(){
        delet[] parent;
    }
    void Union(int root1, int root2);
    int Find(int x);
};

DisjoinSet::DisjoinSet(int n){
    size = n;
    parent = new int[size];

    for (int i = 0; i < size; ++i){
        parent[i] = -1;
    }
}

int DisjoinSet::Find(int x){
    if (parent[x] < 0]){
        return x;
    }
    // 此处有路径压缩
    return parent[x] = Find(parent[x]);
}

void DisjoinSet::Union(int root1, int root2){
    if (root1 == root2){
        return;
    }

    // 按照规模并
    if (parent[root1] > parent[root2]){
        parent[root2] += parent[root1];
        parent[root1] = root2;
    }
    else{
        parent[root1] += parent[root2];
        parent[root2] = root1;
    }
}

例题

  • leetcode 952 题中采用了 Find Union 的求解。但此题难点并不是采用并查集,而并查集的元素是什么,元素应该是具体数字,而并非序号,有利于减少除法运算量。采用 static 的数组存每个数的最大质因素,有利于减少除法运算数量。

Comments

Content