深入理解并查集:原理、实现与优化

深入理解并查集:原理、实现与优化

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

什么是并查集?

并查集(Union-Find)是一种用于处理不相交集合合并及查询问题的树型数据结构。它主要支持两种操作:

  1. 合并(Union):将两个集合合并为一个集合
  2. 查找(Find):确定某个元素属于哪个集合(通常返回集合的代表元素)

并查集在解决连通性问题、图论问题等方面有着广泛的应用,如判断图中两个节点是否连通、计算连通分量数量等。

并查集的核心概念

基本操作

并查集主要支持三种基本操作:

  1. 初始化:将每个元素初始化为一个单独的集合
  2. 合并:将两个元素所在的集合合并为一个集合
  3. 查询:判断两个元素是否属于同一个集合

实现方式

并查集有两种主要的实现思路:

  1. 快速查询实现:基于数组结构,查询快但合并慢
  2. 快速合并实现:基于森林结构,合并快但查询可能较慢

快速查询实现

快速查询实现使用数组来存储每个元素的集合编号(ID)。初始化时,每个元素的ID就是其数组索引。

特点

  • 查询操作:时间复杂度O(1),直接返回数组中的ID
  • 合并操作:时间复杂度O(n),需要遍历整个数组更新ID

代码实现

class QuickFindUF:
    def __init__(self, n):
        self.id = [i for i in range(n)]  # 初始化每个元素的集合ID
    
    def find(self, p):
        return self.id[p]  # 直接返回ID
    
    def union(self, p, q):
        pid = self.id[p]
        qid = self.id[q]
        if pid == qid: return  # 已经在同一集合
        
        # 将所有属于q集合的元素改为p的集合ID
        for i in range(len(self.id)):
            if self.id[i] == qid:
                self.id[i] = pid

快速合并实现

快速合并实现使用树结构来表示集合,每个节点指向其父节点,根节点指向自己。

特点

  • 查询操作:需要从节点向上查找根节点,时间复杂度取决于树的高度
  • 合并操作:只需将一个树的根节点指向另一个树的根节点,时间复杂度O(1)

代码实现

class QuickUnionUF:
    def __init__(self, n):
        self.parent = [i for i in range(n)]  # 初始化父节点数组
    
    def find(self, p):
        # 查找根节点
        while p != self.parent[p]:
            p = self.parent[p]
        return p
    
    def union(self, p, q):
        root_p = self.find(p)
        root_q = self.find(q)
        if root_p == root_q: return  # 已经在同一集合
        
        # 将一个根节点指向另一个根节点
        self.parent[root_p] = root_q

优化策略

路径压缩

路径压缩是在查找过程中优化树结构的方法,可以减少后续查询的时间。有两种主要方式:

  1. 隔代压缩:在查找时,将当前节点指向其祖父节点
  2. 完全压缩:在查找时,将路径上的所有节点直接指向根节点
隔代压缩实现
def find(self, p):
    while p != self.parent[p]:
        self.parent[p] = self.parent[self.parent[p]]  # 指向祖父节点
        p = self.parent[p]
    return p
完全压缩实现
def find(self, p):
    if p != self.parent[p]:
        self.parent[p] = self.find(self.parent[p])  # 递归压缩
    return self.parent[p]

按秩合并

按秩合并是在合并时优化树结构的方法,可以避免树变得过高。有两种方式:

  1. 按深度合并:总是将深度较小的树合并到深度较大的树下
  2. 按大小合并:总是将节点数较少的树合并到节点数较多的树下
按深度合并实现
class UnionFind:
    def __init__(self, n):
        self.parent = [i for i in range(n)]
        self.rank = [1] * n  # 记录每个根的深度
    
    def union(self, p, q):
        root_p = self.find(p)
        root_q = self.find(q)
        if root_p == root_q: return
        
        # 将深度小的树合并到深度大的树下
        if self.rank[root_p] > self.rank[root_q]:
            self.parent[root_q] = root_p
        elif self.rank[root_p] < self.rank[root_q]:
            self.parent[root_p] = root_q
        else:
            self.parent[root_q] = root_p
            self.rank[root_p] += 1  # 深度相同,合并后深度+1
按大小合并实现
class UnionFind:
    def __init__(self, n):
        self.parent = [i for i in range(n)]
        self.size = [1] * n  # 记录每个根的集合大小
    
    def union(self, p, q):
        root_p = self.find(p)
        root_q = self.find(q)
        if root_p == root_q: return
        
        # 将小集合合并到大集合下
        if self.size[root_p] > self.size[root_q]:
            self.parent[root_q] = root_p
            self.size[root_p] += self.size[root_q]
        else:
            self.parent[root_p] = root_q
            self.size[root_q] += self.size[root_p]

并查集的应用

1. 等式方程的可满足性

问题描述:给定一组等式和不等式,判断是否可以同时满足所有条件。

解决思路

  1. 首先处理所有等式,将相等的变量合并到同一集合
  2. 然后检查所有不等式,如果不等式的两个变量在同一集合中,则矛盾

2. 省份数量

问题描述:给定城市之间的连接关系,计算"省份"的数量(连通分量的数量)。

解决思路

  1. 初始化并查集
  2. 遍历所有城市对,如果相连则合并
  3. 最后统计根节点的数量即为省份数量

性能分析

  • 空间复杂度:O(n),需要存储每个元素的父节点信息
  • 时间复杂度
    • 仅路径压缩或仅按秩合并:O(log n)
    • 同时使用路径压缩和按秩合并:接近O(1)

最佳实践

在实际应用中,推荐使用路径压缩(隔代压缩)而不使用按秩合并的实现,因为:

  1. 代码更简单
  2. 性能已经足够好
  3. 避免了维护额外数组的开销
class UnionFind:
    def __init__(self, n):
        self.parent = [i for i in range(n)]
    
    def find(self, p):
        while p != self.parent[p]:
            self.parent[p] = self.parent[self.parent[p]]  # 隔代压缩
            p = self.parent[p]
        return p
    
    def union(self, p, q):
        root_p = self.find(p)
        root_q = self.find(q)
        if root_p == root_q: return
        self.parent[root_p] = root_q

总结

并查集是一种高效处理不相交集合问题的数据结构,通过路径压缩和按秩合并等优化技术,可以使其操作接近常数时间复杂度。掌握并查集的原理和实现,能够帮助我们高效解决许多连通性问题。

LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邱行方Mountain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值