C++并集查找

C++并集查找

前言

C++图论
C++算法与数据结构
本博文代码打包下载

基本概念

并查集(Union-Find)是一种用于处理动态连通性(直接或间接相连)的数据结构,主要支持两种操作:union 和 find。通过这两个基本操作,可以高效地管理一组元素之间的连通关系。
Find: 查找节点所在有向树的根。
Union: 将两个不同的有向图合并为一棵树。

暴力做法

并集查找处理无向图的数据结构:有向森林,每棵树都是内向树。连通子图都直接或间接指向根,根出度为0,其它节点出度为1。vPar记录各节点的父节点。
Find(u)函数寻找u所在有向树的根(最远祖先):

while(-1!= vPar[u]){ u =vPar}return u;

判断u和v是否连通:

returnFind(u)==Find(v)

连通:

root1 =Find(u); root2 =Find(v); 如果root1和root2相等,直接返回。否则会有自环。 vPar[root1]= root2;

初始和合并都是有向森林

可以用数学归纳法证明。
初始:所有连通子图都是只有一个节点的有向树。
合并前后:
root1是根,出度0。合并后不是根,出度为1。
root2合并前后都是根,出度为0,不变。
其它节点合并前后都不是根,出度不变。
合并前后:v节点所在子图都直接或间接指向root2。
合并后:u所在子树通过u间接指向root2。

时间复杂度

合并和查找都是O(n)

启发式合并(UNION BY RANK)

合并前,u、v所在子树的节点数分别为mu、mv。
{ v P a r [ r o o t 1 ] = r o o t 2 u 树所有节点层次 + 1 , v 子树层次不变。 m u < = m v v P a r [ r o o t 2 ] = r o o t 1 v 树所有节点层次 + 1 , u 子树层次不变 其它 \begin{cases} vPar[root1]= root2 & u树所有节点层次+1,v子树层次不变。 & mu <= mv \\ vPar[root2]=root1 & v树所有节点层次+1,u子树层次不变& 其它 \end{cases} {vPar[root1]=root2vPar[root2]=root1​u树所有节点层次+1,v子树层次不变。v树所有节点层次+1,u子树层次不变​mu<=mv其它​
任意节点合并的次数不会超过 l o g 2 n log2n log2n,否则合并此树的节点数超过n。被合并一次层次数+1,故所有节点的层次不互已超过 l o g 2 n log2n log2n。

路径压缩(PATH COMPRESSION)

路径压缩的优点在于它的简单性和有效性。尽管单次 find 的时间复杂度可能较高,但由于摊还分析的结果表明,经过多次操作后,平均时间复杂度接近常数 ( O(\alpha(n)) ),其中 ( \alpha(n) ) 是反阿克曼函数,增长极其缓慢}
u及u所有的祖先都直接连通root2。
合并和查找都是O(log2n)

可撤销并集查找

以启发式合并为基础。每次连通:
vPar[root1]= root2。 我们记录root1、修改之前的x=vPar[root1]。
vCnt[root2] += vCnt[root1],我们记录root2,y=vCnt[root1]。
撤销时:
vPar[root1] = x vCnt[root2] -=y
用栈记录操作记录。已经连通也要有记录。

按秩和并

层次小的指向层次大的,层次不会超过logN,故连通和查询的时间复杂度都是O(logN)。可以实现带权并集查找,主要包括:路径最小边权最大生成树、路径最大边权最小生成树、相同起点终点任意路径边权必须相等。

路径最小边权最大生成树

g( p )是路径p中,最小边权。
f(u,v)是某图中u到v所有路径最大g( p ) ,假定uv连通。
将边权降序排序后,依次枚举uv。如果uv已经连通,忽略;否则连通。
性质一:原图G,并集查找图uf ,任意两点uv的连通性相同。
性质二:原图G,并集查找图uf ,任意两点uv的f(u,v)相等。G的边权就是原始边权。uf中u的根是g1,v的根是g2,如果 g 1 ≠ g 2 g1 \neq g2 g1=g2。如果最终是 g 1 → g 2 g1 \rightarrow g2 g1→g2,则此边的边权(简称g1边)是uv的边权w;如果 g 2 → g 1 g2 \rightarrow g1 g2→g1此边的边权也是w。
如果uv连通,其最最近公共祖先为g,则uf中 u → g → v u \rightarrow g \rightarrow v u→g→v的最小边权,就是G中f(u,v)。
性质三;n1的父节点是n2,n2的父节点是n3。则 n 1 → n 2 的边权 ≥ n 2 → n 3 的边权 n1 \rightarrow n2的边权\ge n2 \rightarrow n3的边权 n1→n2的边权≥n2→n3的边权。先加的边边权大,后加的边权小。只有各子树的根才会增加边,当 n 2 → n 3 n2 \rightarrow n3 n2→n3时,说明n2此时是根,即n1现在及未来不是根。

重构树(Kruskal’s Reconstruction Tree)

可以看成”路径最小边权最大生成树“的进一步扩展。
按边权从大到小排序,如果已经连通忽略。
共2N-1个节点, 0 ∼ N − 1 0 \sim N-1 0∼N−1是真实节点, i ∈ [ N , 2 × N − 1 ] i \in [N,2\times N-1] i∈[N,2×N−1]是虚拟节点,第i-N条有效边(非忽略边)添加后,此边所在区域。求点权就是第 i − N i-N i−N条有效边的边权。真实节点的点权可以看成无限大。
性质一:重构树中,uv的简单路径经过的最小点权。就是原图G中,u、v 任意”路径最小边权“的最大值。
性质二:任意子树x,x节点的点权 ≤ \le ≤任意子孙节点的点权。
性质三:某节点g1的点权 ≥ P \ge P ≥P,g1子树的真实节点u,v,则原图一定存在边权全 ≥ \ge ≥P的路径。即uv在重构树简单路径种经过的虚拟节点对应的边。
性质四:任意真实节点u,某祖先g1,g1的父节点是g2。 g 1 点权 ≥ P > g 2 点权 g1点权 \ge P > g2点权 g1点权≥P>g2点权.非g1子树的任意节点v,都和u不存在边权d都 ≥ P \ge P ≥P的路径。注意:重构树的层次可能是N,所有要用树上倍增求g1,g2。

封装类代码

无向图的并集查找(路径压缩)

classCUnionFind{public:CUnionFind(int iSize):m_vNodeToRegion(iSize){for(int i =0; i < iSize; i++){ m_vNodeToRegion[i]= i;} m_iConnetRegionCount = iSize;}CUnionFind(vector<vector<int>>& vNeiBo):CUnionFind(vNeiBo.size()){for(int i =0; i < vNeiBo.size(); i++){for(constauto& n : vNeiBo[i]){Union(i, n);}}}intGetConnectRegionIndex(int iNode){int& iConnectNO = m_vNodeToRegion[iNode];if(iNode == iConnectNO){return iNode;}return iConnectNO =GetConnectRegionIndex(iConnectNO);}voidUnion(int iNode1,int iNode2){constint iConnectNO1 =GetConnectRegionIndex(iNode1);constint iConnectNO2 =GetConnectRegionIndex(iNode2);if(iConnectNO1 == iConnectNO2){return;} m_iConnetRegionCount--;if(iConnectNO1 > iConnectNO2){ m_vNodeToRegion[iConnectNO1]= iConnectNO2;}else{ m_vNodeToRegion[iConnectNO2]= iConnectNO1;}}boolIsConnect(int iNode1,int iNode2){returnGetConnectRegionIndex(iNode1)==GetConnectRegionIndex(iNode2);}intGetConnetRegionCount()const{return m_iConnetRegionCount;}//vector<int> GetNodeCountOfRegion()//各联通区域的节点数量//{// const int iNodeSize = m_vNodeToRegion.size();// vector<int> vRet(iNodeSize);// for (int i = 0; i < iNodeSize; i++)// {// vRet[GetConnectRegionIndex(i)]++;// }// return vRet;//} std::unordered_map<int, vector<int>>GetNodeOfRegion(){ std::unordered_map<int, vector<int>> ret;constint iNodeSize = m_vNodeToRegion.size();for(int i =0; i < iNodeSize; i++){ ret[GetConnectRegionIndex(i)].emplace_back(i);}return ret;}private: vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引int m_iConnetRegionCount;};

实时更新各连通区域节点的并集查找

//启发式合并,实时更新各连通区域的节点classCUnionFindNodes:publicCUnionFind{public:CUnionFindNodes(int n):CUnionFind(n){ m_nodes.resize(n);for(int i =0; i < n; i++){ m_nodes[i].emplace_back(i);}}voidUnion(int iNode1,int iNode2){int g0 =GetConnectRegionIndex(iNode1);int g1 =GetConnectRegionIndex(iNode2);if(g0 == g1){return;}CUnionFind::Union(iNode1, iNode2);if(g1 ==GetConnectRegionIndex(g0)){swap(g0, g1);}if(m_nodes[g1].size()> m_nodes[g0].size()){swap(m_nodes[g1], m_nodes[g0]);} m_nodes[g0].insert(m_nodes[g0].end(), m_nodes[g1].begin(), m_nodes[g1].end()); m_nodes[g1].clear();} vector<vector<int>> m_nodes;};

无向图的并集查找

如果一条会形成环或出度为2,忽略。

classCUnionFindDirect{public:CUnionFindDirect(int iSize){ m_vRoot.resize(iSize);iota(m_vRoot.begin(), m_vRoot.end(),0);}voidConnect(bool& bConflic,bool& bCyc,int iFrom,int iTo){ bConflic = bCyc =false;if(iFrom != m_vRoot[iFrom]){ bConflic =true;}Fresh(iTo);if(m_vRoot[iTo]== iFrom){ bCyc =true;}if(bConflic || bCyc){return;} m_vRoot[iFrom]= m_vRoot[iTo];}intGetMaxDest(int iFrom){Fresh(iFrom);return m_vRoot[iFrom];}private:intFresh(int iNode){if(m_vRoot[iNode]== iNode){return iNode;}return m_vRoot[iNode]=Fresh(m_vRoot[iNode]);} vector<int> m_vRoot;};

可撤销的并集查找

classCUnDoUnionFind{public:CUnDoUnionFind(int N):m_par(N,-1),m_cnt(N,1){};intFind(int x){while(-1!= m_par[x]){ x = m_par[x];}return x;}voidUnion(int u,int v){int root1 =Find(u);int root2 =Find(v);if(root1 == root2){ m_record.emplace(root1, m_par[root1], root2,0);return;}if(m_cnt[root1]> m_cnt[root2]){swap(u, v);swap(root1, root2);} m_record.emplace(root1, m_par[root1], root2, m_cnt[root1]); m_par[root1]= root2; m_cnt[root2]+= m_cnt[root1];}boolUndo(){if(m_record.empty()){returnfalse;}constauto[root1, par, root2, cnt]= m_record.top(); m_record.pop(); m_par[root1]= par; m_cnt[root2]-= cnt;returntrue;} vector<int> m_par, m_cnt; stack<tuple<int,int,int,int>> m_record;};

带权并集查找

classCUnionFundW{public:CUnionFundW(int N):m_par(N),m_rank(N,1){for(int i =0; i < N; i++){ m_par[i]= i;}}//返回值:{被合并的连通区域的根root1,root1是不是node1的根}。连通的效果是增加边root1到root2。 pair<int,bool>Union(int node1,int node2){constauto&[root1, le1]=GetRootLeve(node1);constauto&[root2, le2]=GetRootLeve(node2);if(root1 == root2){return{-1,false};}if(m_rank[root1]== m_rank[root2]){ m_par[root1]= root2; m_rank[root2]++;return{ root1,true};}elseif(m_rank[root1]< m_rank[root2]){ m_par[root1]= root2;return{ root1,true};} m_par[root2]= root1;return{ root2,false};} tuple<int, vector<int>, vector<int>>LCA(int node1,int node2){//node1和node2相等或不在一个连通区域为空,否则路径的所有的边auto[root1, le1]=GetRootLeve(node1);auto[root2, le2]=GetRootLeve(node2); vector<int> ans1, ans2;if(root1 != root2){returnmake_tuple(-1, ans1, ans2);}for(; le2 > le1; le2--){ ans2.emplace_back(node2); node2 = m_par[node2];}for(; le1 > le2; le1--){ ans1.emplace_back(node1); node1 = m_par[node1];}for(; node1 != node2; node1 = m_par[node1], node2 = m_par[node2]){ ans1.emplace_back(node1); ans2.emplace_back(node2);}returnmake_tuple(node1, ans1, ans2);}boolIsConnet(int node1,int node2){auto[root1, le1]=GetRootLeve(node1);auto[root2, le2]=GetRootLeve(node2);return root1 == root2;} pair<int,int>GetRootLeve(int node)const{int leve =0;int root = node;while(root != m_par[root]){ root = m_par[root]; leve++;}return{ root,leve };} vector<int> m_par; vector<int> m_rank;};

样例

力扣

难度分
【欧拉回路】【图论】【并集查找】765. 情侣牵手
【C++图论 并集查找】2316. 统计无向图中无法互相到达点对数1604
【C++图论 并集查找】1319. 连通网络的操作次数1633
【C++图论 并集查找】2492. 两个城市间路径的最小分数1679
【C++并集查找 图论】1202. 交换字符串中的元素1855
【C++图论 并集查找】924. 尽量减少恶意软件的传播1868
【图论】【并集查找】【C++算法】928. 尽量减少恶意软件的传播 II1985
【C++ 并集查找】1722. 执行交换操作后的最小汉明距离1892
【图论】【广度优先】【 并集查找】2092 找出知晓秘密的所有专家2003
【C++图论 并集查找】947. 移除最多的同行或同列石头2034
C++二分查找或并集查找:2948交换得到字典序最小的数组2047
【并集查找】839. 相似字符串组2053
【C++ 同余 裴蜀定理 中位数贪心 并集查找】2607. 使子数组元素和相等2071
【并集查找 图论 位运算】3108. 带权图里旅途的最小代价2108
【C++图论 并集查找】1579. 保证图可完全遍历2131
【最大公约 调和级数 并集查找】2709. 最大公约数遍历2171
【调和级数 并集查找】1627. 带阈值的图连通性2221
【并集查找 最大公约数 调和数】952. 按公因数计算最大组件大小2272
【并集查找】749. 隔离病毒2277
【并集查找 离线查询】1697. 检查边长度限制的路径是否存在2300
【广度优先搜索】【二分图】【并集查找】2493. 将节点分成尽可能多的组2415
【最大公约数 并集查找 调和级数】1998. 数组的最大公因数排序2429
【并集查找 图论】2421. 好路径的数目2444
【状态压缩 并集查找 图论】2157. 字符串分组2499
【图轮】【 最小生成树】【 并集查找】1489. 找到最小生成树里的关键边和伪关键边2571

洛谷普及+

【二分查找 并集查找】P6004 [USACO20JAN] Wormhole Sort S普及+
【图论 BFS染色 并集查找 】P3663 [USACO17FEB] Why Did the Cow Cross the Road III S普及+
【图论 并集查找】P3671 [USACO17OPEN] Where‘s Bessie? S普及+
【拓扑排序 并集查找】P8269 [USACO22OPEN] Visits S普及+
【贪心 逆向思考 并集查找 数学归纳法】P7162 [COCI 2020/2021 #2] Sjekira普及+
【并集查找】P7299 [USACO21JAN] Dance Mooves S普及+
【并集查找 逆向思考】P8097 [USACO22JAN] Farm Updates G普及+
【位运算 并集查找】P7973 [KSN2021] Binary Land普及+
【并集查找】P7991 [USACO21DEC] Connecting Two Barns S普及+
【并集查找 最小生成树】P8191 [USACO22FEB] Moo Network G普及+
【并集查找 启发性合并】P8710 [蓝桥杯 2020 省 AB1] 网络分析普及+
【并集查找 有向图 逆序思考】P8359 [SNOI2022] 垃圾回收普及+
【并集查找】8210 [THUPC 2022 初赛] 造计算机普及+
【并集查找 拓扑序】P9013 [USACO23JAN] Find and Replace S普及+
【最短路 并集查找】P9327 [CCC 2023 S4] Minimum Cost Roads普及+
【二分图 扩展域并集查找】P1525 [NOIP 2010 提高组] 关押罪犯普及+
【二分图 扩展域并集查找】P11409 西湖有雅座普及+
【单调栈 双连通 并集查找】P12169 [蓝桥杯 2025 省 C/Python A/Java C] 登山普及+
【并集查找】P12274 [蓝桥杯 2024 国 Python B] 设计普及+

样例整理时间

2025-5-25

投票

# 扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步ZEEKLOG学院,听白银讲师(也就是鄙人)的讲解。
https://edu.ZEEKLOG.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.ZEEKLOG.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

Read more

Python 二维码生成工具 Segno:轻量高效的全场景二维码实现指南

Python 二维码生成工具 Segno:轻量高效的全场景二维码实现指南

Python 二维码生成工具 Segno:轻量高效的全场景二维码实现指南 * 一、简介 * 二、标准二维码 * 三、微型二维码 * 四、彩色二维码 * 五、WIFI二维码 * 六、地理二维码 * 七、邮箱二维码 * 八、轻量联系信息二维码 * 九、全面联系信息二维码 * 十、EPC二维码 * 十一、带LOGO二维码 夸克资源分享: 表情包:https://pan.quark.cn/s/5b9ddeb237fe 工具箱:https://pan.quark.cn/s/aa2d6a730482,图吧、美蛋、路遥、入梦等 Fiddler Everywhere抓包:https://pan.quark.

By Ne0inhk
Python开发从入门到精通:异步编程进阶与asyncio高级应用

Python开发从入门到精通:异步编程进阶与asyncio高级应用

《Python开发从入门到精通》设计指南第二十七篇:异步编程进阶与asyncio高级应用 一、学习目标与重点 💡 学习目标:掌握Python异步编程的高级技巧,包括协程调度、任务管理、异步IO操作、并发控制等;学习asyncio库的高级应用;通过实战案例开发异步应用程序。 ⚠️ 学习重点:协程调度、任务管理、异步IO操作、并发控制、asyncio高级应用、异步应用实战。 27.1 异步编程概述 27.1.1 什么是异步编程 异步编程是一种编程模型,通过非阻塞的方式处理IO操作,提高应用程序的并发性能。在异步编程中,任务可以在等待IO操作完成时继续执行其他任务,而不需要阻塞等待。 27.1.2 异步编程的优势 * 高性能:通过非阻塞的方式处理IO操作,提高应用程序的并发性能。 * 高并发:可以同时处理大量的IO操作,提高应用程序的吞吐量。 * 简单易用:使用协程和异步IO操作,代码可读性高。 27.2 asyncio库的高级应用 27.

By Ne0inhk

YOLO X Layout从部署到集成:Python API封装为微服务接入企业内容中台

YOLO X Layout从部署到集成:Python API封装为微服务接入企业内容中台 1. 这不是普通OCR,是真正理解文档结构的“眼睛” 你有没有遇到过这样的问题:扫描了一堆PDF合同、产品说明书或财务报表,想自动提取其中的表格数据、识别标题层级、定位图片位置,却发现传统OCR只能返回一堆乱序的文字?或者用通用目标检测模型去识别文档元素,结果连“页眉”和“页脚”都分不清? YOLO X Layout就是为解决这类问题而生的。它不是简单的文字识别工具,而是一个专门针对文档版面理解训练的视觉模型——能像人一样“看懂”一页文档里哪些是标题、哪些是正文段落、哪里是表格、哪块是配图、甚至能区分公式和脚注。它不只告诉你“这里有字”,而是回答“这是什么类型的元素,在页面什么位置,和其他元素是什么关系”。 更关键的是,它把这种专业能力打包成了开箱即用的服务:有直观的Web界面,也有标准的HTTP API,还能轻松嵌入到你现有的内容处理流水线里。本文就带你从零开始,把YOLO X Layout真正用起来—

By Ne0inhk
【python】懂车帝字体反爬逐层解密案例(附完整代码)

【python】懂车帝字体反爬逐层解密案例(附完整代码)

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,ZEEKLOG全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,PyQt5和Tkinter桌面应用开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生k8s,linux,shell脚本等实操经验,网站搭建等分享。 所属的专栏:爬虫副业实战,零基础、进阶教学 景天的主页:景天科技苑 文章目录 * 懂车帝字体反爬解密 * 1.网站介绍 * 2.抓包分析 * 4.抓取数据分析 * 5.字体文件查找 * 6.字体文件查看 * 7.字体文件识别 * 8.字体识别代码编写 * 9.

By Ne0inhk