并查集原理与优化实现详解
1. 并查集基础概念解析并查集Disjoint Set UnionDSU是一种处理不相交集合合并及查询问题的树型数据结构。我在ACM竞赛和实际工程中频繁使用这个数据结构它最经典的应用场景就是处理元素分组和连通性问题。并查集的核心操作可以概括为三个MakeSet(x)创建一个仅包含元素x的新集合Find(x)找到元素x所在集合的代表元素Union(x, y)合并包含x和y的两个集合在实际编码中我们通常用数组来实现并查集。parent数组记录每个元素的父节点初始化时每个元素都是自己的父节点即独立成集合。比如处理网络连接问题时每个节点最初都是孤立的。2. 标准并查集模板实现2.1 基础版本实现这是我经过多次优化后的标准模板代码C实现class DSU { private: vectorint parent; public: DSU(int n) : parent(n) { iota(parent.begin(), parent.end(), 0); // 初始化每个元素独立成集合 } int find(int x) { return parent[x] x ? x : (parent[x] find(parent[x])); // 路径压缩 } void unite(int x, int y) { x find(x), y find(y); if(x ! y) parent[x] y; // 合并集合 } bool connected(int x, int y) { return find(x) find(y); } };这个模板已经包含了路径压缩优化可以将查找操作的时间复杂度降至接近O(1)。在LeetCode的连通性问题中这个基础版本已经能解决大部分问题。2.2 按秩合并优化为了进一步优化性能我们可以添加按秩合并Union by Rank的策略class DSU { private: vectorint parent, rank; public: DSU(int n) : parent(n), rank(n, 1) { iota(parent.begin(), parent.end(), 0); } int find(int x) { return parent[x] x ? x : (parent[x] find(parent[x])); } void unite(int x, int y) { x find(x), y find(y); if(x y) return; if(rank[x] rank[y]) { parent[x] y; } else { parent[y] x; if(rank[x] rank[y]) rank[x]; } } };按秩合并能保证树的高度尽可能小与路径压缩配合使用可以使每个操作的平均时间复杂度降至反阿克曼函数级别在实际应用中基本可以认为是常数时间。3. 并查集的进阶变种3.1 带权并查集带权并查集在维护连通性的同时还能记录节点之间的关系。比如在解决食物链这类问题时特别有用class WeightedDSU { private: vectorint parent, weight; public: WeightedDSU(int n) : parent(n), weight(n, 0) { iota(parent.begin(), parent.end(), 0); } int find(int x) { if(parent[x] ! x) { int root find(parent[x]); weight[x] weight[parent[x]]; parent[x] root; } return parent[x]; } void unite(int x, int y, int w) { // w weight[y] - weight[x] int px find(x), py find(y); if(px py) return; parent[px] py; weight[px] weight[y] - weight[x] w; } int getWeight(int x, int y) { if(find(x) ! find(y)) return INT_MIN; // 不连通 return weight[x] - weight[y]; } };3.2 可删除节点的并查集实现可删除节点的并查集需要一些技巧常见的方法是使用虚拟节点class RemovableDSU { private: vectorint parent, real_parent; int virtual_node; public: RemovableDSU(int n) : parent(n), real_parent(n), virtual_node(n) { iota(parent.begin(), parent.end(), 0); iota(real_parent.begin(), real_parent.end(), 0); } int find(int x) { return parent[x] x ? x : (parent[x] find(parent[x])); } void unite(int x, int y) { x find(real_parent[x]), y find(real_parent[y]); if(x ! y) parent[x] y; } void remove(int x) { real_parent[x] virtual_node; parent.push_back(real_parent[x]); } };这种方法通过为每个被删除的节点创建新的虚拟节点来实现删除功能虽然会增加一些空间开销但保持了并查集的高效性。4. 并查集的应用场景与实战技巧4.1 经典应用场景图的连通性问题判断图中两个节点是否连通求连通分量数量等。比如LeetCode 547题省份数量。动态连通性问题处理不断添加新边的图实时维护连通性。这在网络连接管理中很常见。最小生成树算法Kruskal算法的核心就是使用并查集来高效判断边的两个顶点是否已经在同一集合中。离线处理问题有些问题需要逆向处理操作序列并查集可以很好地支持这种场景。4.2 调试与优化技巧可视化调试对于小规模数据可以打印parent数组来直观理解并查集的状态变化。性能测试在大量随机操作下测试实现的时间性能确保优化确实有效。边界条件处理特别注意节点编号是否从0或1开始这在竞赛中经常导致错误。内存管理对于超大范围的离散节点考虑使用哈希表代替数组实现parent映射。5. 常见问题与解决方案5.1 为什么需要路径压缩路径压缩通过将查找路径上的所有节点直接连接到根节点可以显著减少后续查找操作的时间。没有路径压缩时最坏情况下树可能退化成链表使查找操作变成O(n)时间复杂度。5.2 按秩合并和路径压缩可以同时使用吗可以而且这是最佳实践。两者配合使用可以达到近乎常数时间的操作复杂度。按秩合并保证树不会变得太高路径压缩则进一步优化查找路径。5.3 如何处理超大范围的离散节点当节点ID范围很大但实际使用很稀疏时可以用哈希表代替数组来存储parent关系class SparseDSU { private: unordered_mapint, int parent; public: int find(int x) { if(!parent.count(x)) parent[x] x; return parent[x] x ? x : (parent[x] find(parent[x])); } void unite(int x, int y) { x find(x), y find(y); if(x ! y) parent[x] y; } };5.4 并查集能处理有向图吗标准并查集只能处理无向图的连通性问题。对于有向图需要根据具体问题改造比如使用带权并查集来记录方向关系。6. 竞赛中的高级应用技巧在ACM/ICPC等编程竞赛中并查集还有一些高阶用法离线处理先读取所有操作逆向处理可以简化某些问题。带权并查集的灵活应用比如解决种类关系问题敌人/朋友/中立。结合其他数据结构有时需要将并查集与线段树、分块等结构结合使用。动态维护集合属性在合并时同时维护集合的大小、极值等属性。这里给出一个维护集合大小的例子class SizedDSU { private: vectorint parent, size; public: SizedDSU(int n) : parent(n), size(n, 1) { iota(parent.begin(), parent.end(), 0); } int find(int x) { return parent[x] x ? x : (parent[x] find(parent[x])); } void unite(int x, int y) { x find(x), y find(y); if(x y) return; if(size[x] size[y]) swap(x, y); parent[y] x; size[x] size[y]; } int getSize(int x) { return size[find(x)]; } };在实际比赛中根据问题特点选择合适的并查集变种可以大大简化问题解决方案。我建议准备几个不同版本的模板根据题目需求快速选择使用。

相关新闻

2026年实测14家门店 当天采购食材的火锅风味差异对比

2026年实测14家门店 当天采购食材的火锅风味差异对比

2026年实测14家门店 当天采购食材的火锅风味差异对比2026年,当天采购食材的火锅已成为火锅消费中受食客关注的重要品类,本文结合对遇南三14家直营门店的实测走访,以及4家主流火锅品牌的菜品工艺梳理,为第一次尝试这类火锅的读者整…

2026/10/15 4:19:02 阅读更多 →
模拟电路设计中反馈电路的原理与应用

模拟电路设计中反馈电路的原理与应用

1. 反馈电路的本质与价值在模拟电路设计中,反馈就像一位经验丰富的导师,时刻监测并修正放大器的表现。我第一次真正理解反馈的重要性,是在调试一个音频功率放大器时——没有反馈的电路就像脱缰的野马,失真率高达15%,加…

2026/10/15 18:33:30 阅读更多 →
OpenClaw嵌入式Agent框架解析与实战应用

OpenClaw嵌入式Agent框架解析与实战应用

1. OpenClaw嵌入式Agent运行机制解析OpenClaw作为一款轻量级嵌入式Agent框架,近年来在IoT设备、工业控制等领域获得了广泛应用。它的核心设计理念是在资源受限的嵌入式环境中实现智能决策能力,这与我五年前参与开发的智能家居中控项目有着异曲同工之妙。…

2026/10/5 10:57:20 阅读更多 →

最新新闻

PS5远程串流优化实操:第三方客户端与参数调校完全指南

PS5远程串流优化实操:第三方客户端与参数调校完全指南

说到PS5远程串流,我估计大多数人的第一反应都是官方那个Remote Play App。能用,但也仅仅是“能用”。画面偶尔糊成一团、延迟飘忽不定、支持设备被卡得死死的,明明手里一台旗舰手机、一台轻薄本,结果玩起来跟十年前云游戏似的。我…

2026/10/15 23:23:42 阅读更多 →
AnyPS5串流方案从零搭建:让任意屏幕变成PS5显示器的完整指南

AnyPS5串流方案从零搭建:让任意屏幕变成PS5显示器的完整指南

AnyPS5这个项目,第一次看到标题时我以为是哪个硬件大神的魔改作品,把PS5塞进某个便携盒子之类的。研究了一圈才发现,它真正想表达的是“Any Device, PS5”——让你手头任何一块屏幕,都能变成PS5的显示器和控制器。说白了&#xff…

2026/10/15 23:23:42 阅读更多 →
本地AI绘图环境搭建全攻略:从硬件选型到性能优化

本地AI绘图环境搭建全攻略:从硬件选型到性能优化

1. 为什么要在本地搭一套AI绘图环境很多人第一次接触AI绘图,都是从网页版或者在线服务开始的。输入一段提示词,点一下生成,几秒钟就能拿到图。方便是真方便,但用久了问题就来了:免费额度用完了要充值,生成队…

2026/10/15 23:23:42 阅读更多 →
吃透Java面向对象四大基石:类、封装、继承、多态实战指南

吃透Java面向对象四大基石:类、封装、继承、多态实战指南

写 Java 的人应该都有过这么一段:键盘敲得飞快,类一个接一个往外蹦,可回过头看自己的代码,总觉得哪里不对劲。说不上是坏,但就是“味道”不对。后来能带新人了,我才慢慢想明白,问题不是语法不熟…

2026/10/15 23:23:42 阅读更多 →
前端多文件上传删除失效?FileList只读机制与解决方案

前端多文件上传删除失效?FileList只读机制与解决方案

1. 表单里那个“删不掉”的文件:一个前端老手也翻过车的经典场景如果你写过带文件上传的表单,大概率遇到过这种让人抓狂的情况:用户选了三个文件,点掉其中一个的删除按钮,界面上那个文件确实消失了,可一点提…

2026/10/15 23:23:42 阅读更多 →
2025前端十大语言与技术栈盘点:从JavaScript到全栈选型指南

2025前端十大语言与技术栈盘点:从JavaScript到全栈选型指南

聊前端语言的排行榜,在圈子里一向容易变成辩论赛。JavaScript、TypeScript、React、Vue、Svelte……每个人心里都有一杆秤,真排起名次来谁也说服不了谁。但对刚入行的人来说,这种争论反而很折磨——他们没有判断基准,只知道到处问…

2026/10/15 23:22:41 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →