经典算法:并查集(DSU)结构简介

经典算法:并查集(DSU)结构简介Python 笔记 并查集 DSU 结构简介 1 并查集是什么 2 并查集原理 3 并查集代码实现 1 一般代码实现 2 优化的 DSU 结构 1 调整树形结构 2 每次查找后更新节点信息 4 Leetcode 例题分析 1 Leetcode547 FriendCircle Leetcode721 AccountsMerg Leetcode128 LongestConse Leetcode1579 Rem

1. 并查集是什么

并查集(Disjoint Set Union)是一种常用的处理不相交集合间的合并与查找功能树形结构,配合与之对应的联合-搜索算法(Union Find Algorithm),可以将不相交集合间的合并与查找功能的时间复杂度大幅缩减至 O ( l o g N ) O(logN) O(logN)乃至 O ( 1 ) O(1) O(1)的量级。

2. 并查集原理

并查集的核心思想在于将所有相互关联的集合都用一个代表元素进行表征,这样,只要找到两个元素对应的代表元素,判断其是否一致即可判断他们是否属于同一个集合;同样的,如果需要联合两个原本不相交的集合,只要将其中一个代表元素指向另一个代表元素,使他们采用同一个代表元素即可

更详细的原理说明可以参考下面的参考链接3中知乎专栏里的讲解,他对并查集原理的说明讲的非常的详细,我主要就是通过的这篇专栏学习的并查集相关内容。

3. 并查集代码实现

1. 一般代码实现

下面,我们来给出一般的并查集的简单代码实现。

class DSU: def __init__(self, N): self.root = [i for i in range(N)] def find(self, k): if self.root[k] == k: return k return self.find(self.root[k]) def union(self, a, b): x = self.find(a) y = self.find(b) if x != y: self.root[y] = x return 

上述代码即为最为一般性的并查集结构。

2. 优化的DSU结构

不过,通常而言,为了更好地提高算法效率,我们有时会给其增加一些小的trick。比如:

1. 调整树形结构

为了更好地优化算法的效率,我们可以控制树形结构,使其尽可能地扁平化,避免出现链型结构导致深度过深,我们经常会通过记录树的深度的方式优化树形结构,从而优化算法效率

class DSU: def __init__(self, N): self.root = [i for i in range(N)] self.depth = [1 for i in range(N)] def find(self, k): if self.root[k] == k: return k return self.find(self.root[k]) def union(self, a, b): x = self.find(a) y = self.find(b) xh = self.depth[x] yh = self.depth[y] if x == y: return if xh >= yh: self.root[y] = x self.depth[x] = max(self.depth[x], self.depth[y]+1) else: self.root[x] = y 

2. 每次查找后更新节点信息

另一种常用的trick为:

  • 我们可以在每次查找完成之后都直接更新节点的父节点信息为最顶部父节点,即整个集合的代表元,那么下一次查找的时间复杂度就会直接降至 O ( 1 ) O(1) O(1)
class DSU: def __init__(self, N): self.root = [i for i in range(N)] def find(self, k): if self.root[k] == k: return k self.root[k] = self.find(self.root[k]) return self.root[k] def union(self, a, b): x = self.find(a) y = self.find(b) if x != y: self.root[y] = x return 

4. Leetcode例题分析

下面,我们来通过一些leetcode中的例题来考察并查集结构的实际用法。

1. Leetcode 547. Friend Circles

这一题是最为典型的并查集使用场景,我们直接套用并查集结构就能解答这道题。

直接给出代码实现如下:

class DSU: def __init__(self, N): self.root = [i for i in range(N)] def find(self, k): if self.root[k] == k: return k return self.find(self.root[k]) def union(self, a, b): x = self.find(a) y = self.find(b) if x != y: self.root[y] = x return class Solution: def findCircleNum(self, M: List[List[int]]) -> int: n = len(M) dsu = DSU(n) for i in range(n): for j in range(i+1, n): if M[i][j] == 1: dsu.union(i, j) group = set() for i in range(n): group.add(dsu.find(i)) return len(group) 

2. Leetcode 721. Accounts Merge

这一题较之上一题会显得多少复杂一点,但是还是比较明显的DSU结构,我们不是根据数字,而是根据account建立dsu关系,而后根据不同的代表account返回去找到对应的账户所有人即可

给出代码实现如下:

class DSU: def __init__(self): self.dsu = { 
   } def find(self, account): if account not in self.dsu: self.dsu[account] = account return account if account == self.dsu[account]: return account self.dsu[account] = self.find(self.dsu[account]) return self.dsu[account] def union(self, x, y): a1 = self.find(x) a2 = self.find(y) self.dsu[a2] = a1 return class Solution: def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: mapping = { 
   } dsu = DSU() for it in accounts: name = it[0] key_account = it[1] mapping[key_account] = name for account in it[2:]: mapping[account] = name dsu.union(key_account, account) res = defaultdict(list) for account in mapping: key_account = dsu.find(account) res[key_account].append(account) ans = [[mapping[k]] + sorted(v) for k, v in res.items()] return ans 

3. Leetcode 128. Longest Consecutive Sequence

这一题的DSU结构还是比较明显的,就是针对数组中的每一个元素n,查看n-1以及n+1是否也出现在dsu当中,如果在的话就连结这几个元素,反之就不连接。

唯一需要注意的是,由于这一题对于执行效率有较高的要求,因此,我们需要对dsu的树状结构进行优化,使其尽可能地扁平化。

给出一种代码实现如下:

class DSU: def __init__(self): self.dsu = { 
   } def find(self, n): if n not in self.dsu: return None if n == self.dsu[n]: return n self.dsu[n] = self.find(self.dsu[n]) return self.dsu[n] def union(self, x, y): xr = self.find(x) yr = self.find(y) if xr is None or yr is None: return self.dsu[yr] = xr return def add(self, n): if n not in self.dsu: self.dsu[n] = n return class Solution: def longestConsecutive(self, nums: List[int]) -> int: if nums == []: return 0 dsu = DSU() nums = list(set(nums)) for n in nums: dsu.add(n) dsu.union(n, n-1) dsu.union(n, n+1) counter = defaultdict(int) for n in nums: counter[dsu.find(n)] += 1 return max(counter.values()) 

4. Leetcode 1579. Remove Max Number of Edges to Keep Graph Fully Traversable

这一题是leetcode Weekly Contest 205的最后一题,当时没能做出来,现在,在大致学会了DSU结构之后,我们重新来考察这道题。

主体的思想还是和当时保持一致,当某条边连接的两点已经属于同一个集合时,我们就舍弃掉这条边,反之将这条边保留,最后看是否能够构成一个全连接图,如果能的话,一共删除了几条边

通过DSU结构,我们很快地搞定了这道题,给出我们自己实现的python代码如下:

from copy import deepcopy class DSU: def __init__(self, n): self.dsu = [i for i in range(n+1)] def find(self, x): if x == self.dsu[x]: return x self.dsu[x] = self.find(self.dsu[x]) return self.dsu[x] def union(self, x, y): xr = self.find(x) yr = self.find(y) self.dsu[yr] = xr return class Solution: def maxNumEdgesToRemove(self, n: int, edges: List[List[int]]) -> int: alice = [] bob = [] both = [] for t, x, y in edges: if t == 1: alice.append((x, y)) elif t == 2: bob.append((x, y)) else: both.append((x, y)) dsu = DSU(n) counter3 = 0 for x, y in both: if dsu.find(x) == dsu.find(y): continue dsu.union(x, y) counter3 += 1 dsu1 = deepcopy(dsu) counter1 = 0 for x, y in alice: if dsu1.find(x) == dsu1.find(y): continue dsu1.union(x, y) counter1 += 1 dsu2 = deepcopy(dsu) counter2 = 0 for x, y in bob: if dsu2.find(x) == dsu2.find(y): continue dsu2.union(x, y) counter2 += 1 if counter1 + counter3 != n-1 or counter2 + counter3 != n-1: return -1 else: return len(edges) + counter3 - 2*n +2 

5. 参考链接

  1. Disjoint Set Union (DSU) 并查集及其应用
  2. 并查集(Disjoint Set)
  3. 算法学习笔记(1) : 并查集
  4. wikipedia: 并查集
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/212885.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午7:01
下一篇 2026年3月18日 下午7:02


相关推荐

  • 怎么安装pycharm及环境变量配置_JRE环境配置

    怎么安装pycharm及环境变量配置_JRE环境配置pycharm安装以及其环境的配置说明此次我们使用win10系统安装pycharm的64位社区版,并且对Anaconda3中自带的Python3进行环境的配置,如果您没有Anaconda3甚至是没有Python3环境,可以参考Anaconda3安装教程及说明,如果您的pip源未更改,这里推荐您改为使用国内的pip源,这样可以更快的下载组件,方法见修改pip源至国内镜像网站。教程从开始菜单中找到你的AnacondaPrompt并打开…

    2022年8月25日
    10
  • 为啥国人偏爱Mybatis,而老外喜欢Hibernate/JPA呢?

    为啥国人偏爱Mybatis,而老外喜欢Hibernate/JPA呢?关于SQL和ORM的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行了一番讨论,感触还是有一些,于是就有了今天这篇文。声明:本文不会下关于Mybatis和JPA两个持久层框架哪个更好这样的结论。只是摆事实,讲道理,所以,请各位看官勿喷。一、事件起因关于Mybatis和JPA孰优孰劣的问题,争论已经很多年了。一直也没有结论,毕竟每个人的喜好和习惯是大不相同的。我也看…

    2022年10月20日
    4
  • PyQt5 QPixmap简介

    PyQt5 QPixmap简介转载自 https blog csdn net jia article details 前言 QPixmap 类用于绘图设备的图像显示 它可以作为一个 QPainterDevi 对象 也可以加载到一个控件中 通常是标签或者按钮 用于在标签或按钮上显示图像 QPixmap 可以读取的图像文件类型有 BMP GIF JPG 等 QPixmap 类中常用的方

    2026年3月18日
    2
  • css 变量操作

    css 变量操作js 设置变量 dom 节点 style setProperty 名称 内容 js 读取变量 方式一 dom 节点 style getPropertyV 名称 该方式只能读取 setProperty 设置的变量 不能读取在 css 中的变量 但都能设置 css 变量 方式二 getComputedS dom 节点 getPropertyV 名称 能读取 css 中设置的变量和 setProperty 设置的变量 但不能 setProp

    2026年3月19日
    2
  • 基础概念 — SOP「建议收藏」

    基础概念 — SOP「建议收藏」StandardOperationProcedure定义精髓企业sop提高企业运行效率提升企业运行效果步骤1)确定流程2)明确步骤3)制定SOP4)执行操作定义标准作业程序将某一事件的标准操作步骤和要求以统一的格式扫描出来,用来指导和规范日常的工作精髓将细节进行量化即对某一程序中的关键控制点进行细化和量化企业sop提高企业运行效率企业的日常工作有两个基本的特征,一是许多岗位的人员经常会发生变动,二是一些日常的工作的基本作业程序相对比较稳定。通过SOP对细节进行量化和规范例如:在一

    2022年5月9日
    53
  • 特征归一化处理_归一化法要求

    特征归一化处理_归一化法要求版权声明:本文为博主原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。…

    2025年6月5日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号