为了账号安全,请及时绑定邮箱和手机立即绑定

并查集教程:入门与实践指南

概述

并查集(Disjoint Set Union)是一种高效处理不相交集合合并与查询问题的数据结构,广泛应用于连通性问题、动态联盟问题等多种场景。本文将详细介绍并查集的基本概念、应用场景、实现方法以及优化技巧,并通过实例进一步说明其在实际问题中的应用。

并查集简介

并查集的基本概念

并查集(Disjoint Set Union,简称 DSU)是一种用于处理不相交集合的合并与查询问题的数据结构。它由一组动态集合构成,每个集合中的元素互不相同,并且每个集合都有一个唯一的代表元素。并查集有两种基本操作:合并(Union)和查找(Find)。

  • 合并:将两个不同的集合合并成一个集合。
  • 查找:查找某元素所在的集合。

并查集的重要特性是支持在几乎常数时间内完成合并和查找操作。并查集通常用于解决连通性问题、动态联盟问题等。

并查集的应用场景

并查集在很多场景中都有应用,以下是几种典型的应用场景:

  • 连通性问题:例如,确定一个图中各个连通分量的大小,或者在一个无向图中判断两点是否连通。
  • 动态联盟问题:例如,模拟社交网络中的用户关系,或者在团队协作中检查用户是否属于同一个分组。
  • 路径压缩:通过路径压缩技术优化查询操作,使得查找操作的时间复杂度接近于常数。
  • 按秩合并:通过增加额外的逻辑,使得合并操作更加高效。
并查集的实现

并查集的数据结构

并查集通常使用数组或哈希表来实现。数组形式通常用于元素的索引与元素本身一一对应的情况;哈希表则适用于元素的索引与元素不一一对应的情况。这里以数组实现为例。

数据结构通常包含以下两个部分:

  • parent 数组:用于存储每个元素的父节点。parent[i] 表示元素 i 的父节点。根节点(即没有父节点)自身的 parent[i] == i
  • rank 数组(可选):用于维护每个节点的树的深度,用于按秩合并优化。
class DisjointSet:
    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n  # Optional: for path compression and union by rank

    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # Path compression
        return self.parent[x]

    def union(self, x, y):
        rootX = self.find(x)
        rootY = self.find(y)

        if rootX != rootY:
            if self.rank[rootX] < self.rank[rootY]:
                self.parent[rootX] = rootY
            elif self.rank[rootX] > self.rank[rootY]:
                self.parent[rootY] = rootX
            else:
                self.parent[rootY] = rootX
                self.rank[rootX] += 1

并查集的基本操作(查找、合并)

  • 查找操作find):查找某个元素所在的根节点。

    def find(self, x):
      if self.parent[x] != x:
          self.parent[x] = self.find(self.parent[x])  # Path compression
      return self.parent[x]
  • 合并操作union):将两个集合合并为一个集合。

    def union(self, x, y):
      rootX = self.find(x)
      rootY = self.find(y)
    
      if rootX != rootY:
          if self.rank[rootX] < self.rank[rootY]:
              self.parent[rootX] = rootY
          elif self.rank[rootX] > self.rank[rootY]:
              self.parent[rootY] = rootX
          else:
              self.parent[rootY] = rootX
              self.rank[rootX] += 1
并查集的优化

路径压缩

路径压缩是一种优化方法,用于在查找操作中压缩树的高度,使得查找操作的复杂度接近于常数。具体来说,当查找某个节点时,会将该节点到根节点路径上的所有节点的父节点直接指向根节点。

def find(self, x):
    if self.parent[x] != x:
        self.parent[x] = self.find(self.parent[x])  # Path compression
    return self.parent[x]

按秩合并

按秩合并是一种优化方法,用于在合并操作中减少树的高度。具体来说,合并时总是将较小树的根节点指向较大的树的根节点,这样可以保证合并后的树深度不会增加太多。

def union(self, x, y):
    rootX = self.find(x)
    rootY = self.find(y)

    if rootX != rootY:
        if self.rank[rootX] < self.rank[rootY]:
            self.parent[rootX] = rootY
        elif self.rank[rootX] > self.rank[rootY]:
            self.parent[rootY] = rootX
        else:
            self.parent[rootY] = rootX
            self.rank[rootX] += 1
并查集的实例应用

并查集解决连通性问题

给定一个无向图,判断两点是否连通。可以使用并查集来解决这个问题。

def isConnected(graph, src, dest):
    ds = DisjointSet(len(graph))

    for u in range(len(graph)):
        for v in graph[u]:
            ds.union(u, v)

    return ds.find(src) == ds.find(dest)

并查集解决动态联盟问题

动态联盟问题,例如在社交网络中,模拟用户之间的联盟关系。可以使用并查集维护各个联盟,并在需要时合并或查询联盟。

def mergeAlliances(people, alliances):
    ds = DisjointSet(len(people))

    for alliance in alliances:
        for person in alliance[1:]:
            ds.union(alliance[0], person)

    # Check if two people belong to the same alliance
    for personA, personB in pairs:
        if ds.find(personA) == ds.find(personB):
            print(f"{personA} and {personB} are in the same alliance.")
        else:
            print(f"{personA} and {personB} are not in the same alliance.")
并查集的进阶使用

并查集与哈希表的结合

在某些应用场景中,节点的索引和节点本身可能不一一对应。此时,可以使用哈希表来存储节点与其索引的映射关系。

class DisjointSetWithHash:
    def __init__(self):
        self.parent = {}
        self.rank = {}

    def find(self, x):
        if x not in self.parent:
            self.parent[x] = x
            self.rank[x] = 0
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # Path compression
        return self.parent[x]

    def union(self, x, y):
        rootX = self.find(x)
        rootY = self.find(y)

        if rootX != rootY:
            if self.rank[rootX] < self.rank[rootY]:
                self.parent[rootX] = rootY
            elif self.rank[rootX] > self.rank[rootY]:
                self.parent[rootY] = rootX
            else:
                self.parent[rootY] = rootX
                self.rank[rootX] += 1

并查集的变种应用

并查集还可以用于解决其他问题,例如判断一个图是否存在环。

def hasCycle(graph):
    ds = DisjointSet(len(graph))

    for u in range(len(graph)):
        for v in graph[u]:
            rootU = ds.find(u)
            rootV = ds.find(v)

            if rootU == rootV:
                return True
            ds.union(rootU, rootV)

    return False
总结与练习

并查集常见问题解答

  • 并查集的时间复杂度是多少?
    并查集的时间复杂度在路径压缩和按秩合并的优化下,接近于常数时间,即O(1)。

  • 并查集适合处理什么类型的问题?
    并查集适合处理不相交集合的合并与查询问题,如图的连通性问题、动态联盟问题等。

  • 路径压缩和按秩合并有什么区别?
    路径压缩优化的是查找操作,使得查找操作的时间复杂度接近于常数;按秩合并优化的是合并操作,使得合并操作的树深度不会增加太多。

并查集相关练习题推荐

推荐一些并查集相关练习题,帮助你更好地理解和应用并查集。

  1. 连通性问题

    • 给定一个无向图,判断每个点是否在一个连通分量中。
    • 给定一个图,判断图中是否存在环。
  2. 动态联盟问题

    • 在社交网络中,模拟用户之间的联盟关系,判断是否存在公共联盟。
    • 在团队协作中,检查用户是否属于同一个分组。
  3. 其他应用
    • 在图论中,使用并查集解决最小生成树问题。
    • 在字符串匹配中,使用并查集优化 KMP 算法。

通过解决这些练习题,可以帮助你更好地掌握并查集的使用技巧和应用场景。可以参考MooC网上的相关课程进行学习和练习。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消