我的并查集学习总结

并查集(Union-Find)算法详解

一、并查集的应用场景

🎯 主要应用领域

  • 动态连通性问题:判断两个元素是否连通
  • 集合合并与查询:高效的集合操作
  • 网络连接检测:计算机网络、社交网络
  • 图论算法:Kruskal最小生成树算法
  • 图像处理:连通区域标记

💡 典型问题

  • 朋友圈问题
  • 家庭关系合并
  • 网络连通性检测
  • 岛屿数量问题
  • 最小生成树

二、并查集的原理

🔍 核心思想

树形结构表示集合:每个集合用一棵树表示,树根作为集合的代表元素。

📐 算法特性

  • 使用父指针数组表示树结构
  • 支持两种核心操作:查找(Find)和合并(Union)
  • 通过优化达到接近常数时间复杂度

三、通用模板

// 并查集数据结构
class UnionFind {
private:
    vector<int> parent;  // 父节点数组
    vector<int> rank;    // 秩数组(用于按秩合并)
    int count;           // 连通分量数量


    UnionFind(int n) {
        count = n;
        parent.resize(n);
        rank.resize(n, 0);  // 初始秩都为0
        
        // 每个元素的父节点指向自己
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    // 2. 查找操作:找到元素x的根节点(带路径压缩)
    int find(int x) {
        // 2.1 如果x不是根节点,递归查找并压缩路径
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 路径压缩
        }
        // 2.2 返回根节点
        return parent[x];
    }
    
    // 2. 查找操作,循环搜索
    int find(int x) {
	while(x != father[x]) {
		x = father[x];
	}
	return x;
	}



    // 3. 合并操作:将元素x和y所在的集合合并(按秩合并)
    void unionSet(int x, int y) {
        // 3.1 找到x和y的根节点
        int rootX = find(x);
        int rootY = find(y);
        
        // 3.2 如果已经在同一集合,直接返回
        if (rootX == rootY) return;
        
        // 3.3 按秩合并:将秩小的树合并到秩大的树下
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            // 秩相等时,任意合并,并增加秩
            parent[rootY] = rootX;
            rank[rootX]++;
        }
        
        // 3.4 连通分量数量减1
        count--;
    }

    // 4. 判断连通性:检查x和y是否属于同一集合
    bool connected(int x, int y) {
        return find(x) == find(y);
    }

    // 5. 获取连通分量数量
    int getCount() {
        return count;
    }
};

// 使用示例:
void main() {
    // 初始化并查集
    UnionFind uf(n);
    
    // 处理连接关系
    for (每条边 (u, v)) {
        if (!uf.connected(u, v)) {
            uf.unionSet(u, v);
        }
    }
    
    // 获取结果
    int components = uf.getCount();
}

四、并查集的实现步骤

🚀 详细步骤

  1. 初始化:每个元素独立成集合,父指针指向自己
  2. 查找操作
    • 递归找到根节点
    • 路径压缩优化
  3. 合并操作
    • 找到两个元素的根节点
    • 按秩合并优化
  4. 连通性判断:比较根节点是否相同

📝 关键要点

  • 路径压缩:让查找路径上的节点直接指向根节点
  • 按秩合并:将矮树合并到高树下,保持树平衡
  • 代表元素:每个集合用最小元素或任意元素作为代表

五、代码示例(C++实现)

示例1:基础并查集

#include <iostream>
#include <vector>
using namespace std;

class UnionFind {
private:
    vector<int> parent;
    
public:
    UnionFind(int n) : parent(n) {
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
    
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    
    void unionSet(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            parent[rootY] = rootX;
        }
    }
    
    bool connected(int x, int y) {
        return find(x) == find(y);
    }
};

示例2:统计集合数量

int countComponents(int n, vector<vector<int>>& edges) {
    UnionFind uf(n);
    for (auto& edge : edges) {
        uf.unionSet(edge[0], edge[1]);
    }
    
    // 统计根节点数量
    int count = 0;
    for (int i = 0; i < n; i++) {
        if (uf.find(i) == i) {
            count++;
        }
    }
    return count;
}

六、时间复杂度

操作基础版本优化版本
初始化O(n)O(n)
查找(Find)O(h)O(α(n))
合并(Union)O(h)O(α(n))

其中:

  • h是树的高度
  • α(n)是反阿克曼函数,增长极慢,可视为常数

七、并查集与BFS、DFS的对比

特性并查集BFSDFS
主要用途动态连通性最短路径、遍历路径搜索、回溯
数据结构父指针数组队列栈/递归
时间复杂度O(α(n))O(V+E)O(V+E)
空间复杂度O(n)O(V)O(h)
适用场景集合操作层次遍历深度搜索

八、总结

✨ 核心优势

  • 高效性:接近常数时间的操作
  • 简洁性:代码实现简单
  • 实用性:解决多种连通性问题

🎯 适用场景选择

  • 使用并查集:动态连通性、集合合并
  • 使用BFS:最短路径、层次遍历
  • 使用DFS:路径存在性、回溯问题

💡 学习建议

  1. 掌握基础版本,理解核心思想
  2. 学习优化技术,理解其原理
  3. 多做练习,熟悉各种应用场景
  4. 对比不同算法,选择合适的解决方案

并查集是解决连通性问题的利器,在算法竞赛和工程实践中都有广泛应用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值