定义


并查集

并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。
并查集
并查集支持两个操作:

  • 查询(Find):判断两个元素是否属于同一集合
  • 合并(Union):合并两个元素所属集合

初始化

初始时,每个元素都是一个单独的集合。方便起见,将每个元素的父亲设为自己。

struct UnionFind  
{  
    vector<int> parent; // 父节点  
    explicit UnionFind(const int size) : parent(size)  
    {  
        iota(parent.begin(), parent.end(), 0); // 初始化父节点, parent[i] = i  
    }  
};

查询

找到当前节点的根节点。

路径压缩

因为查询的结果为根节点,如果当前节点不是根节点或根节点的子节点,就将当前节点直接到根节点,这样下次查询就可以不用再查找了。
路径压缩

实现

struct UnionFind  
{  
    vector<int> parent; // 父节点
    int find(const int x)  
    {  
        if (parent[x] == x)  
            return parent[x];  
        return parent[x] = find(parent[x]); // 路径压缩  
    }  
};

合并

要合并两棵树,我们只需要将一棵树的根节点连到另一棵树的根节点。

启发式合并

合并时,选择哪棵树的根节点作为新树的根节点会影响未来操作的复杂度。我们可以将秩(即深度)较小的树连到另一棵,以免发生退化。

实现

struct UnionFind  
{  
    vector<int> parent; // 父节点  
    vector<int> rank; // 秩  
    void unite(int x, int y)  
    {  
        x = find(x), y = find(y);  
        if (x == y)  
            return;  
        if (rank[x] > rank[y])  
        {  
            parent[y] = x;  
            rank[x] += rank[y];  
        }  
        else  
        {  
            parent[x] = y;  
            rank[y] += rank[x];  
        }  
    }  
};

模版

typedef struct {  
    vector<int> parent; // 父节点  
    vector<int> rank; // 秩  
    explicit UnionFind(const int size) : parent(size), rank(size, 1)  
    {  
        iota(parent.begin(), parent.end(), 0); // 初始化父节点, parent[i] = i  
    }  
  
    int find(const int x)  
    {  
        if (parent[x] == x)  
            return parent[x];  
        return parent[x] = find(parent[x]); // 路径压缩  
    }  
  
    void unite(int x, int y)  
    {  
        x = find(x), y = find(y);  
        if (x == y)  
            return;  
        if (rank[x] > rank[y])  
        {  
            parent[y] = x;  
            rank[x] += rank[y];  
        }  
        else  
        {  
            parent[x] = y;  
            rank[y] += rank[x];  
        }  
    }  
} UnionFind;

带权并查集

在普通并查集的基础上,维护节点到父节点的边权。

struct WeightedUnionFind  
{  
    vector<int> parents; // 父节点  
    vector<int> weight; // 权重,规定weight[x] - weight[root] = value , weight[root] = 0  
    explicit WeightedUnionFind(const int size) : parents(size + 1), weight(size + 1)  
    {  
        iota(parents.begin(), parents.end(), 0); // 初始化父节点, parent[i] = i  
    }  
  
    int find(const int x)  
    {  
        if (parents[x] != x)  
        {  
            const int parent = parents[x]; // weight[x] = value (x -> parent)  
			parents[x] = find(parent); // 路径压缩,保证 weight[parent] = value' (parent -> root)
			weight[x] += weight[parent]; // weight[x] = value + value' (x -> parent -> root)  
        }  
        return parents[x];  
    }  
  
    void unite(const int x, const int y, const int value)  
    {  
        const int fx = find(x), fy = find(y);  
        if (fx == fy)  
            return;  
        parents[fx] = fy;  
        // 因为weight[x] + weight[fx] - weight[y] = value (x -> fx -> fy -> y)  
        // 所以weight[fx] = weight[y] - weight[x] + value  
        weight[fx] = weight[y] - weight[x] + value;  
    }  
  
    int different(const int x, const int y)  
    {  
        if (find(x) != find(y)) // 不在同一集合  
            return -1;  
        return weight[x] - weight[y];  
    }  
};