并查集的套路

并查集(Union-Find)主要用于处理动态连通性问题,常见套路包括以下几个方面:


1. 基本操作

  • 初始化
    vector<int> parent(n);
    iota(parent.begin(), parent.end(), 0);  // 每个节点的父节点初始化为自己
    
  • 查找(Find)
    int find(int x) {
        if (parent[x] != x) 
            parent[x] = find(parent[x]);  // 路径压缩
        return parent[x];
    }
    
  • 合并(Union)
    void unite(int x, int y) {
        parent[find(x)] = find(y);
    }
    

2. 优化策略

  • 路径压缩(Path Compression):在 find 过程中,将树的深度压缩,加速后续查询。
  • 按秩合并(Union by Rank):合并时,总是将低秩树挂在高秩树上,减少树的高度。
    vector<int> rank(n, 1);  // 初始秩为1
    
    void unite(int x, int y) {
        int rootX = find(x), 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]++;
            }
        }
    }
    

3. 常见应用套路

(1) 连接问题

题型:判断两点是否连通、计算连通分量数量。
例题LeetCode 547. 省份数量

int findCircleNum(vector<vector<int>>& isConnected) {
    int n = isConnected.size();
    vector<int> parent(n);
    iota(parent.begin(), parent.end(), 0);
    
    function<int(int)> find = [&](int x) {
        return parent[x] == x ? x : (parent[x] = find(parent[x]));
    };
    
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            if (isConnected[i][j])
                parent[find(i)] = find(j);
    
    unordered_set<int> provinces;
    for (int i = 0; i < n; i++)
        provinces.insert(find(i));
    
    return provinces.size();
}

(2) 冗余连接

题型:给定一张无向图,判断它是否存在环。
例题LeetCode 684. 冗余连接

vector<int> findRedundantConnection(vector<vector<int>>& edges) {
    int n = edges.size();
    vector<int> parent(n + 1);
    iota(parent.begin(), parent.end(), 0);

    function<int(int)> find = [&](int x) {
        return parent[x] == x ? x : (parent[x] = find(parent[x]));
    };

    for (auto& edge : edges) {
        int u = edge[0], v = edge[1];
        if (find(u) == find(v)) return edge;  // 发现环
        parent[find(u)] = find(v);
    }
    return {};
}

(3) 最小生成树(Kruskal 算法)

题型:最小代价连接所有点。
例题LeetCode 1584. 连接所有点的最小费用

int minCostConnectPoints(vector<vector<int>>& points) {
    int n = points.size();
    vector<tuple<int, int, int>> edges;
    
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            edges.push_back({abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]), i, j});
    
    sort(edges.begin(), edges.end());
    vector<int> parent(n);
    iota(parent.begin(), parent.end(), 0);

    function<int(int)> find = [&](int x) {
        return parent[x] == x ? x : (parent[x] = find(parent[x]));
    };

    int cost = 0, count = 0;
    for (auto& [w, u, v] : edges) {
        if (find(u) != find(v)) {
            parent[find(u)] = find(v);
            cost += w;
            if (++count == n - 1) break;
        }
    }
    return cost;
}

4. 总结套路

  1. 查找是否属于同一集合find(x) == find(y)
  2. 合并集合unite(x, y)
  3. 路径压缩 & 按秩合并优化查询效率
  4. 应用方向
    • 计算连通分量
    • 检测环
    • 最小生成树(Kruskal 算法)
    • 解决等式/不等式约束问题

这样整理后,你可以在不同题型中直接套用核心模板,提高解题效率 🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值