当前位置: 首页 > news >正文

【数据结构】 并查集 + 路径压缩与按秩合并 python

目录

  • 前言
  • 模板
  • 朴素实现
  • 路径压缩
  • 按秩合并
    • 按树高为秩
    • 按节点数为秩
  • 总结


前言


并查集的基本实现通常使用森林来表示不同的集合,每个集合用一棵树表示,树的每个节点有一个指向其父节点的指针。
如果一个节点是它自己的父节点,那么它就是该集合的代表(称为根节点)。



模板


P3367 【模板】并查集 https://www.luogu.com.cn/problem/P3367


题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 N , M N,M N,M ,表示共有 N N N 个元素和 M M M 个操作。

接下来 M M M 行,每行包含三个整数 Z i , X i , Y i Z_i,X_i,Y_i Zi,Xi,Yi

Z i = 1 Z_i=1 Zi=1 时,将 X i X_i Xi Y i Y_i Yi 所在的集合合并。

Z i = 2 Z_i=2 Zi=2 时,输出 X i X_i Xi Y i Y_i Yi 是否在同一集合内,是的输出
Y ;否则输出 N

输出格式

对于每一个 Z i = 2 Z_i=2 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

**样例输入 **

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

样例输出

N
Y
N
Y

提示

对于 15 % 15\% 15% 的数据, N ≤ 10 N \le 10 N10 M ≤ 20 M \le 20 M20

对于 35 % 35\% 35% 的数据, N ≤ 100 N \le 100 N100 M ≤ 1 0 3 M \le 10^3 M103

对于 50 % 50\% 50% 的数据, 1 ≤ N ≤ 1 0 4 1\le N \le 10^4 1N104 1 ≤ M ≤ 2 × 1 0 5 1\le M \le 2\times 10^5 1M2×105

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1\le N\le 2\times 10^5 1N2×105 1 ≤ M ≤ 1 0 6 1\le M\le 10^6 1M106 1 ≤ X i , Y i ≤ N 1 \le X_i, Y_i \le N 1Xi,YiN Z i ∈ { 1 , 2 } Z_i \in \{ 1, 2 \} Zi{1,2}


朴素实现


code:

# 在集合中查找元素的根节点
def find(x):if x != pre[x]:return find(pre[x])return x# 将两个集合合并为一个集合
def union(x, y, pre):x_root = find(x)y_root = find(y)pre[x_root] = y_rootn, m = map(int, input().split())
pre = [0] * (n + 1)
for i in range(n):pre[i] = i  # 初始化
for _ in range(m):op, x, y = map(int, input().split())if op == 1:union(x, y, pre)else:if find(x) == find(y):print('Y')else:print('N')

在这里插入图片描述


事实证明:我们需要进行时间上的优化



路径压缩


由于在查询过程中只关心根结点是什么,所以我们可以在在集合在查找元素的同时,把集合中所有的元素都直接指向根节点,减少查找的时间


示例code

def find(x):if pre[x] != x:pre[x] = find(pre[x])  # 在回溯时进行路径压缩return pre[x]

tips:可能会破坏原本的结构



按秩合并


之前我们在合并时,是随机合并两个集合
虽然都能得到正确的结果,但存在时间复杂度的差异
怎样降低时间复杂度呢?
通过按秩合并(启发式合并)

秩”可以理解为树的高度树的节点数 这两种方式
在合并两棵树时,总是把较矮的树挂到较高的树上,节点较小的树挂在节点较多的树上
这种策略有助于保持树的平衡,从而降低查找操作的时间复杂度。

怎么实现?用一个数组记录每个集合的高度或节点数



按树高为秩

示例:

# 将两个集合合并为一个集合
def union(x, y):x_root = find(x)y_root = find(y)if x_root != y_root:# 谁高,谁就作为根节点if rank[x_root] > rank[y_root]:pre[y_root] = x_rootelif rank[x_root] < rank[y_root]:pre[x_root] = y_rootelse:pre[x_root] = y_rootrank[y_root] += 1
# 合并是把小的树直接接到根节点上,所以只有两颗树的高度相等的时候合并后高度才会增加


按节点数为秩

示例:

# 将两个集合合并为一个集合
def union(x, y):x_root = find(x)y_root = find(y)if x_root != y_root:# 谁的节点数多,谁就作为根节点if size[x_root] > size[y_root]:pre[y_root] = x_rootsize[x_root] += size[y_root]else:pre[x_root] = y_rootsize[y_root] += size[x_root]


题解code1(路径压缩+按节点数为秩合并):

# 在集合中查找元素的根节点
def find(x):global preif pre[x] != x:pre[x] = find(pre[x])  # 在回溯时进行路径压缩return pre[x]# 将两个集合合并为一个集合
def union(x, y):global pre, sizex_root = find(x)y_root = find(y)if x_root != y_root:# 谁的节点数多,谁就作为根节点if size[x_root] > size[y_root]:pre[y_root] = x_rootsize[x_root] += size[y_root]else:pre[x_root] = y_rootsize[y_root] += size[x_root]n, m = map(int, input().split())
pre = list(range(n + 1))  # 初始化pre数组
size = [1] * (n + 1)  # 初始化size数组
for _ in range(m):op, x, y = map(int, input().split())if op == 1:union(x, y)else:if find(x) == find(y):print('Y')else:print('N')

路径压缩与按节点大小合并完全兼容


题解code2(按树高为秩合并):

# 在集合中查找元素的根节点
def find(x):global preif pre[x] != x:pre[x] = find(pre[x])  # 在回溯时进行路径压缩return pre[x]# 将两个集合合并为一个集合
def union(x, y):global pre, rankx_root = find(x)y_root = find(y)if x_root != y_root:# 谁高,谁就作为根节点if rank[x_root] > rank[y_root]:pre[y_root] = x_rootelif rank[x_root] < rank[y_root]:pre[x_root] = y_rootelse:pre[x_root] = y_rootrank[y_root] += 1
# 合并是把小的树直接接到根节点上,所以只有两颗树的高度相等的时候合并后高度才会增加n, m = map(int, input().split())
pre = list(range(n + 1))  # 初始化pre数组
rank = [1] * (n + 1)  # 初始化rank数组
for _ in range(m):op, x, y = map(int, input().split())if op == 1:union(x, y)else:if find(x) == find(y):print('Y')else:print('N')

路径压缩不完全与按树高合并兼容,因为路径压缩可以改变树的高度。


总结


并查集(Union-Find 或 Disjoint Set Union, DSU)是一种数据结构,主要用于处理一些不相交集合的合并及查询问题。


如果有更多问题或需要进一步的帮助,可以在评论区留言讨论哦!
如果喜欢的话,请给博主点个关注 谢谢

http://www.lryc.cn/news/528134.html

相关文章:

  • 无耳科技 Solon v3.0.7 发布(2025农历新年版)
  • UART、I2C和SPI对比
  • Vue 响应式渲染 - 待办事项简单实现
  • ResNeSt: Split-Attention Networks论文学习笔记
  • 澳洲硕士毕业论文写作中如何把握主题
  • STM32 LED呼吸灯
  • Java数据库操作指南:快速上手JDBC【学术会议-2025年数字化教育与信息技术(DEIT 2025】
  • 2024年个人总结
  • GitHub 仓库的 Archived 功能详解:中英双语
  • LeetCode:56.合并区间
  • Vue演练场基础知识(七)插槽
  • 进程池的制作(linux进程间通信,匿名管道... ...)
  • 【Linux】Linux C比较两个 IPv6 网关地址是否相等,包括前缀
  • 【uniapp】uniapp使用java线程池
  • 面试题-Java集合框架
  • Java基础教程(007):方法的重载与方法的练习
  • 【ESP32】ESP-IDF开发 | WiFi开发 | TCP传输控制协议 + TCP服务器和客户端例程
  • npm cnpm pnpm npx yarn的区别
  • debian12.9编译freeswitch1.10.12【默认安装】
  • 使用 C/C++ 调用 libcurl 调试消息
  • 【愚公系列】《循序渐进Vue.js 3.x前端开发实践》030-自定义组件的插槽Mixin
  • 大一计算机的自学总结:异或运算
  • 通过protoc工具生成proto的pb.go文件以及使用protoc-go-inject-tag工具注入自定义标签
  • C语言练习(29)
  • Android实训九 数据存储和访问
  • 实验一---典型环节及其阶跃响应---自动控制原理实验课
  • SOME/IP--协议英文原文讲解2
  • matlab中,fill命令用法
  • 【Linux】Linux C判断两个IPv6地址是否有包含关系
  • 【玩转全栈】----Django基本配置和介绍