C++ 并查集

"learning……"

Posted by Ryan on January 9, 2025

你有一千个名字念在嘴边,却只是为了掩盖心中的那一个……

并查集

并查集(Union-Find)是一种数据结构,主要用于处理一些不交集(Disjoint Sets)的合并及查询问题。并查集通常用于图论中的连通性问题,如判断两个元素是否属于同一集合、合并两个集合等。并查集通常使用路径压缩按秩合并(按大小合并) 两种优化技术,使得查询和合并操作的时间复杂度接近常数时间,通常为 O(α(n)),其中α为阿克曼函数的反函数,增长速度非常慢。

核心思想:在最初,每个元素都是一个独立的“帮派”,即每个元素的parent都是自己(初始化)。然后根据个体间的联系,独立的个体合并成一个大的“帮派”,合并的方式是,所有的个体元素认一个元素为“帮派老大”,即parent[i]指向唯一的“老大”(合并操作),具体表现为两个元素之间竞争“帮派老大”。在合并的过程中,需要查询合并的两个独立个体元素是否在一个“帮派”中,也就是是否有同一个“老大”,查询的同时进行路径压缩,即所有人的“老大”更新为同一个(查询操作和路径压缩)。

并查集的基本操作

  • find(x):查找元素 x 所在的集合,返回该集合的代表元。
  • union(x, y):将元素 x 和元素 y 合并到同一集合中。
  • connected(x, y):判断元素 x 和元素 y 是否在同一集合中。

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <vector>
using namespace std;

class UnionFind {
private:
    vector<int> parent;
    vector<int> rank;  // 用于按秩合并,避免树形结构过高

public:
    // 构造函数,初始化 parent 数组和 rank 数组
    UnionFind(int size) {
        parent.resize(size);
        rank.resize(size, 0);  // 初始时所有节点的秩为 0
        for (int i = 0; i < size; ++i) {
            parent[i] = i;  // 每个节点的父节点是它自己
        }
    }

    // 查找操作,带路径压缩
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 路径压缩
        }
        return parent[x];
    }

    // 合并操作,按秩合并
    void unionSets(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (rootX != rootY) {
            // 按秩合并,确保树的高度最小
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                rank[rootX]++;  // 如果秩相同,合并时根的秩加 1
            }
        }
    }

    // 判断两个元素是否属于同一集合
    bool connected(int x, int y) {
        return find(x) == find(y);
    }
};

int main() {
    UnionFind uf(10);  // 创建一个包含 10 个元素的并查集

    // 合并一些集合
    uf.unionSets(1, 2);
    uf.unionSets(2, 3);
    uf.unionSets(4, 5);
    uf.unionSets(6, 7);

    // 判断集合的连接性
    cout << "1 and 3 connected: " << uf.connected(1, 3) << endl;  // 输出 1 (true)
    cout << "1 and 4 connected: " << uf.connected(1, 4) << endl;  // 输出 0 (false)

    uf.unionSets(3, 4);
    cout << "1 and 4 connected after union: " << uf.connected(1, 4) << endl;  // 输出 1 (true)

    return 0;
}

leetcode.547省份数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public:
    int Find(vector<int>&parent,int index)
    {
        if(parent[index]!=index)
        {
            parent[index]=Find(parent,parent[index]);
        }
        return parent[index];
    }
    void Union(vector<int>&parent,int i,int j)
    {
        parent[Find(parent,i)]=Find(parent,j);
    }
    int findCircleNum(vector<vector<int>>& isConnected) {
        int n=isConnected.size();
        vector<int>parent(n);
        for(int i=0;i<n;i++)
        {
            parent[i]=i;
        }
        for(int i=0;i<n;i++)
        {
            for(int j=i+1;j<n;j++)
            {
                if(isConnected[i][j]==1)
                {
                    Union(parent,i,j);
                }
            }
        }
        int cnt=0;
        for(int i=0;i<n;++i)
        {
            if(parent[i]==i) cnt++;
        }
        return cnt;
    }
};