跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 并查集与家谱树应用

C++ 并查集数据结构用于处理不相交集合的合并与查询问题。通过亲戚关系判断和连通图求解两个经典案例,讲解并查集的定义、核心操作并与查。重点阐述了路径压缩优化技巧,降低查找祖先的时间复杂度。提供了完整的 C++ 代码实现,包括初始化、查找根节点、合并集合及统计连通块数量的逻辑。适用于解决家族关系判定、网络连通性分析等场景。

颠三倒四发布于 2026/3/16更新于 2026/10/2797 浏览
C++ 并查集与家谱树应用

亲戚关系问题

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述

规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。

输入格式

第一行:三个整数 n,m,p,(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。

以下 m 行:每行两个数 Mi,Mj,1≤Mi,Mj≤n,表示 Mi 和 Mj 具有亲戚关系。

接下来 p 行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。

输出格式

p 行,每行一个 Yes 或 No。表示第 i 个询问的答案为'具有'或'不具有'亲戚关系。

输入输出样例

输入 #1

6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6

输出 #1

Yes Yes No

并查集

上面的题就是并查集的板子。我们仍然先抛开它不谈,讲讲并查集。

并查集,顾名思义,'并'就是合并,'查'就是查询。首先先讲定义。

定义

在下文中,我们定义:

fa[i] 为 i 的父亲。

find(i) 的值是 i 的祖先(不只是它的父亲,是它父亲的父亲……也就是根)。

思路

如何判断 i 和 j 是否是亲戚?根据上文的定义,我们知道 find(i) 是 i 的祖先。那么判断 i 和 j 是否是亲戚,只需要看他们的祖先是否是一个人就行了。生活常识,如果两个人的祖先一样,那他们就是亲戚。

既然关键在这里,那么 find 函数又该怎么实现呢?接下来,我们讲并查集的'并'。

'并'

用 find 函数来实现'并',首先它的参数为 x,表示需要返回 x 的祖先。

如何找到一个节点的祖先?是的,找到它的父亲,然后是父亲的父亲……直到找到某位父亲,其没有父亲即可。这个节点就是祖先。

这个反复往上'找父亲'的过程,很容易让人想到递归。

x 的父亲是 fa[x],fa[x] 的父亲是 fa[fa[x]]……如何判断一个节点没有父亲呢?初始化时,我们将所有节点的父亲都设置成它自己。(在输入的时候给它真正的父亲,若没有,则代表它是一个祖先)

for(int i=1;i<=n;i++) fa[i]=i;

所以如果它的 fa(父亲)是它自己,那么它就是祖先。我们找到了递归的出口。

反之,如果它有父亲呢?

往上跳(递归),直到跳到出口(祖先)。

所以 find 函数(找祖先)的代码:

int find(int x) { if(fa[x]==x) return x;//祖先的编号(自己) else return fa[x];//往上跳 }

路径压缩

想一下,每一次查询,都要往上跳一遍,非常浪费时间。而且'并'的过程似乎没有体现出来。

如何让查询的时间变少呢?

我们会发现,其实 fa[i] 是不重要的,i 的父亲只是作为中介来寻找祖先;重要的是 i 的祖先。所以,这棵家族的树结构也是不重要的。

我们的目标是,能够保证一次就跳到祖先。

这个过程叫做'路径压缩'。

更改 find 函数的代码,解释见注释:

int find(int x) { if(fa[x]==x) return x; else return fa[x]=find(fa[x]); /*这句话相当于 fa[x]=find(fa[x]); return fa[x]; */ //使得 x 的父亲变成了 x 的祖先 }

构建树

为什么是树?因为父亲和孩子的关系就是树结构,下同。

假设有点 u 和点 v,要将它们合并。

这里比较简单,上代码,注释在关键的地方有解释。

void un(int u,int v) { //找到 u 和 v 各自的祖先 int rootu=find(u); int rootv=find(v); if(rootu!=rootv) {//它们不在同一家族 fa[rootv]=rootu; //v 的祖先变成,u 祖先的儿子 } }

'查'

前面也提到了,如果 i 和 j 是亲戚,那么他们有着一样的祖先。它们的祖先分别是 find(u) 和 find(v)。

判断它们是否为亲戚,直接检查其祖先是否相同即可。代码:

if(find(u)==find(v)) cout<<"Yes"<<endl; else cout<<"No"<<endl;

代码

分步的实现已经完成,接下来将它们拼接到一起。

回到题目 【P1551 亲戚】。

#include<iostream> using namespace std; int n,m,p,fa[5002]; int find(int x) { if(fa[x]==x) return x; return fa[x]=find(fa[x]); }//找祖先 int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);//快读快写 cin>>n>>m>>p; for(int i=1;i<=n;i++) fa[i]=i;//初始化 for(int i=0,a,b;i<m;i++) { cin>>a>>b; fa[find(b)]=find(a); }//合并 while(p--) { int i,j; cin>>i>>j; if(find(i)==find(j)) puts("Yes"); else puts("No"); } return 0; }

求连通块

连通图问题

题目描述

给定一张包含 n 个结点与 m 条边的无向图,结点依次以 1,2,…,n 编号,第 i 条边(1≤i≤m)连接结点 ui 与结点 vi。如果从一个结点经过若干条边可以到达另一个结点,则称这两个结点是连通的。

你需要向图中加入若干条边,使得图中任意两个结点都是连通的。请你求出最少需要加入的边的条数。

注意给出的图中可能包含重边与自环。

输入格式

第一行,两个正整数 n,m,表示图的点数与边数。

接下来 m 行,每行两个正整数 ui,vi,表示图中一条连接结点 ui 与结点 vi 的边。

输出格式

输出一行,一个整数,表示使得图中任意两个结点连通所需加入的边的最少数量。

输入输出样例

输入 #1

4 4 1 2 2 3 3 1 1 4

输出 #1

0

输入 #2

6 4 1 2 2 3 3 1 6 5

输出 #2

2

解法

这是比较经典的问题了。求出要添加多少条边,就是问有多少连通块。

一个连通块就有一棵类似树的结构,有多少祖先就有多少连通块,最后减一即可。详细见上文提到的题解。求祖先数量就是并查集啦。

#include<iostream> #include<vector> using namespace std; int m,n; int fa[100005]; int find(int x) { if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } void add(int x,int y) { int roox=find(x),rooy=find(y); if(roox!=rooy) fa[rooy]=roox; } int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>m; for(int i=1;i<=n;i++) fa[i]=i; for(int i=0;i<m;i++) { int u,v; cin>>u>>v; add(u,v); } int cnt=0; for(int i=1;i<=n;i++) { if(fa[i]==i)//父亲是自己,就是祖先 ++cnt; } cout<<cnt-1; return 0; }

相似题

连通块相似题:洛谷 P1536 村村通(偶然发现的,差不多)。

模板题:P3367【模板】并查集。

目录

  1. 亲戚关系问题
  2. 题目背景
  3. 题目描述
  4. 输入格式
  5. 输出格式
  6. 输入输出样例
  7. 并查集
  8. 定义
  9. 思路
  10. “并”
  11. 路径压缩
  12. 构建树
  13. “查”
  14. 代码
  15. 求连通块
  16. 连通图问题
  17. 题目描述
  18. 输入格式
  19. 输出格式
  20. 输入输出样例
  21. 解法
  22. 相似题

更多推荐文章

查看全部
  • 基于 Python Flask 的电影票房数据采集分析与可视化系统
  • 链式二叉树知识补充:层序遍历与创建销毁
  • 数据结构:堆与优先级队列原理及 Java 实现
  • DeepSeek 中冷启动数据与多阶段训练的作用
  • RoboChallenge 具身智能年度报告:4 万次真机评测揭示模型真实能力
  • 从零开始学习大模型:如何构建与部署开源大语言模型
  • C++ 异常详解:抛出、捕获与异常安全
  • Linux 缓冲区和文件系统
  • 拆解 CASIC MOTOR 机器人底盘无刷减速电机
  • 使用 Llama3 与 DeepChat 搭建本地智能写作工作流
  • 基于 Java 与高德地图的县域烟花销售点 GIS 盘点实践
  • 大模型会取代程序员吗?哪些岗位风险最高
  • Redis Hash 核心操作与 C++ 实践
  • 图文内容社区数据指标体系构建方法与实践
  • Java 中间件:RabbitMQ 延迟队列(死信交换机实现)
  • 机器人激光加工离线编程软件的技术架构与优势分析
  • 飞算 JavaAI 代码审查落地:4 个关键细节与实践
  • OpenViking 实战:AI 代理上下文数据库部署与应用
  • 使用 Rokid 灵珠平台搭建旅游 AR 智能体
  • LangChain 提示模板详解:从基础到进阶

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online