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

数据结构--并查集-高效处理连通性问题

目录

一、理论基础

(1)并查集的功能及实现原理

 (2)代码模版

(3)模拟过程

(4)应用

二、基础题练习

(1)寻找存在的路径(模版题)

(2)排座位

 (3)部落

(4)红色警报

 (5)冗余连接


一、理论基础

(1)并查集的功能及实现原理

首先要知道并查集可以解决什么问题呢?

当我们需要判断两个元素是否在同一个集合里的时候,我们就要想到用并查集。

并查集主要有两个功能:

  • 将两个元素添加到一个集合中。
  • 判断两个元素在不在同一个集合

 如何将两个元素添加到一个集合中?

        将三个元素A,B,C (分别是数字)放在同一个集合,其实就是将三个元素连通在一起,如何连通呢。

        只需要用一个一维数组来表示,即:father[A] = B,father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)。

// 将v,u 这条边加入并查集
void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}

        find函数是如何实现的呢?其实就是通过数组下标找到数组元素,一层一层寻根过程,代码如下:

// 并查集里寻根的过程
int find(int u) {if (u == father[u]) return u; // 如果根就是自己,直接返回else return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找
}

路径压缩:

// 并查集里寻根的过程
int find(int u) {if (u == father[u]) return u;else return father[u] = find(father[u]); // 路径压缩
}

默认自己指向自己,所以

// 并查集初始化
void init() {for (int i = 0; i < n; ++i) {father[i] = i;}
}

如何判断两个元素是否在同一个集合里?

         如果通过 find函数 找到 两个元素属于同一个根的话,那么这两个元素就是同一个集合,代码如下:

// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}

 (2)代码模版

int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构// 并查集初始化
void init() {for (int i = 0; i < n; ++i) {father[i] = i;}
}
// 并查集里寻根的过程
int find(int u) {if (u == father[u]) return u;else return father[u] = find(father[u]); // 路径压缩
}// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}// 将v->u 这条边加入并查集
void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}

通过模板,我们可以知道,并查集主要有三个功能。

  1. 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
  2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
  3. 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点

(3)模拟过程

join(3, 8) 在图中为什么 将 元素1 连向元素 3 而不是将 元素 8 连向 元素 3 呢?

        因为join(int u, int v)函数里 要分别对 u 和 v 寻根之后再进行关联。

 

  为什么 图中 8 又直接指向了 3 了呢?

        代码在寻找根的过程中,会有路径压缩的优化        ,减少 下次查询的路径长度。

 

这里为什么是 2 指向了 6,因为 9的根为 2,所以用2指向6。

(4)应用

连通性问题(图论):判断两点是否在同一个连通块、连通块计数

        【把图中每条边两端的点用并查集合并,判断某两个点是否在同一个集合里,就等于判断它们是否在同一个连通块中。查询连通性:判断两个点 ab 是否连通,只需看 find(a) == find(b) 是否成立。】

        【遍历所有节点,统计父节点为自身的节点数量(即根节点数量),即为连通块个数。if (fa[s] == s) landcount++;

最小生成树 

二、基础题练习

(1)寻找存在的路径(模版题)

      

 并查集基础题目,题目中各个点是双向图链接,那么判断 一个顶点到另一个顶点有没有有效路径其实就是看这两个顶点是否在同一个集合里。

此时我们就可以直接套用并查集模板。

        使用 join(int u, int v)将每条边加入到并查集-----> isSame(int u, int v) 判断是否是同一个根 就可以了。

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int father[N];
int n; // 节点数量// 并查集里寻根的过程
int find(int x) {if( x==father[x]) return x;else return father[x]=find(father[x]);
}// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}// 将v->u 这条边加入并查集
void merge(int u, int v) {int uu = find(u); // 寻找u的根int vv = find(v); // 寻找v的根if (uu == vv) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[vv] = uu;
}int main() {int m, s, t, source, destination;cin >> n >> m;//初始化并查集for (int i = 1; i <= n; i++)  father[i] = i;//合并集合while (m--) {cin >> s >> t;merge(s, t);}//查询是够在一个集合cin >> source >> destination;//判断if (isSame(source, destination)) cout << 1 << endl;else cout << 0 << endl;
}

 

(2)排座位

#include <bits/stdc++.h> 
using namespace std;const int N = 110;       // 最大节点数
int fa[N], g[N][N];      // fa数组表示并查集的父节点,g是邻接矩阵记录关系(朋友或敌人)// 并查集的查找函数,带路径压缩
int find(int x) {if (x == fa[x]) return x;           // 如果当前节点是自己的根,返回它自己else return fa[x] = find(fa[x]);    // 否则递归查找,并路径压缩
}// 并查集的合并函数
void merge(int x, int y) {int xx = find(x);   // 找到x的根int yy = find(y);   // 找到y的根if (xx == yy) return; // 如果已经在同一个集合中,不用合并fa[yy] = xx;          // 否则把y的集合合并到x中return;
}int main() {int n, m, k;cin >> n >> m >> k;  // n个点,m条关系,k次查询// 初始化并查集:每个点的父亲是自己for (int i = 1; i < N; i++) fa[i] = i;// 输入m条关系for (int i = 0; i < m; i++) {int x, y, z;cin >> x >> y >> z;g[x][y] = g[y][x] = z; // 建图,g[x][y] = z,z是关系类型(1表示朋友,-1表示敌人)if (z == 1)            // 如果是朋友merge(x, y);      // 用并查集把他们合并为一个集合}// k 次查询while (k--) {int x, y;cin >> x >> y;int xx = find(x), yy = find(y); // 找到两个点所在的集合根if (xx == yy && g[x][y] != -1)       // 如果在一个集合,并且不是敌人cout << "No problem" << endl;else if (xx != yy && g[x][y] != -1)  // 如果不在一个集合,但不是敌人(可能认识但不熟)cout << "OK" << endl;else if (xx == yy && g[x][y] == -1)  // 如果在一个集合但是敌人(朋友里有矛盾)cout << "OK but..." << endl;else                                 // 不在一个集合,又是敌人(八竿子打不着)cout << "No way" << endl;}
}

 (3)部落

 

#include <bits/stdc++.h>
using namespace std;const int N = 1e4 + 10; // 最大编号范围,保证能容纳所有人
int fa[N];              // 并查集的父亲数组
map<int, bool> st;      // 用于记录输入中出现过的人(防止0~N全部初始化)// 查找函数(带路径压缩)
int find(int x) {if (x == fa[x]) return x;else return fa[x] = find(fa[x]);
}// 合并函数:把x和y所在的集合合并
void merge(int x, int y) {int xx = find(x), yy = find(y);if (xx == yy) return;fa[yy] = xx; // 将y的根指向x的根return;
}int main() {int n;cin >> n;// 初始化:fa[i] = ifor (int i = 0; i < N; i++) fa[i] = i;// 处理输入的朋友圈信息for (int i = 0; i < n; i++) {int k, first_;cin >> k; // 该组朋友圈人数for (int j = 0; j < k; j++) {if (j == 0) {cin >> first_;           // 记录第一个人if (st.count(first_) == 0) st[first_] = 1; // 记录这个人出现了} else {int x;cin >> x;               // 输入其余的人if (st.count(x) == 0) st[x] = 1; // 记录新出现的人merge(x, first_);       // 把当前人和第一个人合并(建朋友圈)}}}// 输出所有出现过的人数cout << st.size() << " ";// 统计朋友圈数量(根节点数量)int num = 0;for (int i = 0; i < N; i++) {if (fa[i] == i && st.count(i) != 0) // 是自己父亲+出现过的人num++;}cout << num << endl;// 查询部分int q;cin >> q;while (q--) {int x, y;cin >> x >> y;if (find(x) == find(y)) cout << "Y" << endl; // 在同一个朋友圈else cout << "N" << endl;                    // 不在一个朋友圈}return 0;
}

(4)红色警报

并查集、结构体存储,边失效标记,,计算初始连通快-->计算处理后的 连通快,看看是不是一样,没用什么算法哇

#include <bits/stdc++.h>
using namespace std;// 最大城市数量为 510,最大边数为 5010
const int N = 510, M = 5010;// 并查集数组,fa[i] 表示 i 所属集合的根节点
int fa[N];// 查找集合根节点,同时路径压缩优化
int find(int x) {if (x == fa[x]) return x;return fa[x] = find(fa[x]);
}// 合并两个集合
void merge(int x, int y) {int xx = find(x), yy = find(y);if (xx == yy) return;  // 已在同一集合中fa[xx] = yy;
}// 表示一条边
struct nod {int x, y;       // 连接的两个城市bool flag = false;  // 标记该边是否失效(因为城市被摧毁)
} edge[M];int main() {int n, m;cin >> n >> m;  // 输入城市数量 n 和边的数量 mint landcount = 0;  // 当前的连通块数量(相当于“陆地”数量)// 初始化并查集,每个城市自己是一个集合for (int s = 0; s < n; s++) fa[s] = s;// 输入边并构建初始连通图for (int i = 0; i < m; i++) {cin >> edge[i].x >> edge[i].y;merge(edge[i].x, edge[i].y);}// 统计初始连通块数量for (int s = 0; s < n; s++)if (fa[s] == s) landcount++;int k;cin >> k;  // 输入即将摧毁的城市数量// 开始逐步摧毁城市for (int ss = 0; ss < k; ss++) {int lostcity;cin >> lostcity;  // 当前摧毁的城市编号int nowlandcount = 0;  // 当前连通块数量// 重置并查集for (int s = 0; s < n; s++) fa[s] = s;// 重新构建图,排除已失效的边或涉及当前摧毁城市的边for (int i = 0; i < m; i++) {if (edge[i].flag || edge[i].x == lostcity || edge[i].y == lostcity) {edge[i].flag = true;  // 标记这条边永久失效continue;}merge(edge[i].x, edge[i].y);  // 合并有效边}// 统计当前连通块数量for (int s = 0; s < n; s++)if (fa[s] == s) nowlandcount++;// 判断是否触发红色警报(连通块数增加 2 或更多)if (nowlandcount >= landcount + 2) {cout << "Red Alert: City " << lostcity << " is lost!\n";} else {cout << "City " << lostcity << " is lost.\n";}landcount = nowlandcount;  // 更新连通块数量}// 如果所有城市都被摧毁,游戏结束if (k == n) cout << "Game Over.";return 0;
}

 (5)冗余连接

 

        题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。

        从前向后遍历每一条边(因为优先让前面的边连上),边的两个节点如果不在同一个集合,就加入集合(即:同一个根节点)。如果

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int father[N];// 并查集里寻根的过程
int find(int u) {if(u==father[u]) return u;else return father[u]=find(father[u]);
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {u = find(u);v = find(v);return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {u = find(u); // 寻找u的根v = find(v); // 寻找v的根if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回father[v] = u;
}int main() {int n;cin >> n;//初始化并查集for (int i = 0; i <= n; ++i) {father[i] = i;}for (int i = 0; i < n; i++) {int s,t;cin >> s >> t;if (isSame(s, t)) {  //已经在一个集合里面 输出cout << s << " " << t << endl;return 0;} else {join(s, t);}}
}

边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了。输出

 

相关文章:

数据结构--并查集-高效处理连通性问题

目录 一、理论基础 &#xff08;1&#xff09;并查集的功能及实现原理 &#xff08;2&#xff09;代码模版 &#xff08;3&#xff09;模拟过程 &#xff08;4&#xff09;应用 二、基础题练习 &#xff08;1&#xff09;寻找存在的路径&#xff08;模版题&#xff09; …...

PLOG安装

Plog可以通过以下命令安装 cd ~ && git clone https://github.com/SergiusTheBest/plog.gitcd plog && mkdir buildcd build && cmake ..make && sudo make installcd ~ && sudo rm -rf ./plog若无法科学上网&#xff0c;可使用git cl…...

LeetCode 热题 100_分割等和子集(89_416_中等_C++)(动态规划)

LeetCode 热题 100_分割等和子集&#xff08;89_416&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;动态规划&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;动态规划&#xff0…...

WPS Office安卓版云文档同步速度与PDF转换体验测评

WPS Office安卓版是很多人常用的移动办公软件。它支持在线编辑、文档同步、格式转换等功能&#xff0c;适合手机和平板用户随时处理文档。我们用它配合谷歌浏览器打开网页文档时&#xff0c;也可以将内容快速保存到云端或转换成PDF格式使用。 先说云文档同步。在打开WPS Office…...

Eureka、LoadBalance和Nacos

Eureka、LoadBalance和Nacos 一.Eureka引入1.注册中心2.CAP理论3.常见的注册中心 二.Eureka介绍1.搭建Eureka Server 注册中心2.搭建服务注册3.服务发现 三.负载均衡LoadBalance1.问题引入2.服务端负载均衡3.客户端负载均衡4.Spring Cloud LoadBalancer1).快速上手2)负载均衡策…...

【Linux网络】构建基于UDP的简单聊天室系统

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…...

【每天一个知识点】大模型的幻觉问题

“大模型的幻觉问题”是指大语言模型&#xff08;如GPT系列、BERT衍生模型等&#xff09;在生成内容时&#xff0c;产生不符合事实或逻辑的虚假信息&#xff0c;即所谓的“幻觉”&#xff08;hallucination&#xff09;。这在诸如问答、摘要、翻译、代码生成等任务中尤其常见。…...

机器学习06-RNN

RNN&#xff08;循环神经网络&#xff09;学习笔记 一、RNN 概述 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一类以序列数据为输入&#xff0c;在序列的演进方向进行递归且所有节点&#xff08;循环单元&#xff09;按链式连接的递归神…...

[大模型]什么是function calling?

什么是function calling&#xff1f; 大模型的 ​​Function Calling​​&#xff08;函数调用&#xff09;是一种让大语言模型&#xff08;如 GPT、Claude 等&#xff09;与外部工具、API 或自定义函数交互的机制。 它的核心目的是让模型能够根据用户的需求&#xff0c;​​…...

C语言高频面试题——嵌入式系统中中断服务程序

在嵌入式系统中&#xff0c;中断服务程序&#xff08;ISR&#xff09;的设计需遵循严格的规则以确保系统稳定性和实时性。以下是对这段代码的分析及改进建议&#xff1a; 代码分析 __interrupt double compute_area (double radius) { double area PI * radius * radius; pri…...

Java高频面试之并发编程-05

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;线程有哪些调度方法&#xff1f; 在Java中&#xff0c;线程的调用方法主要包括以下几种方式&#xff0c;每种方式适用于…...

野外价值观:在真实世界的语言模型互动中发现并分析价值观

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

【Linux】47.高级IO(1)

文章目录 1. 高级IO1.1 五种IO模型1.2 高级IO重要概念1.2.1 同步通信 vs 异步通信1.2.2 阻塞 vs 非阻塞 1.3非阻塞IO1.3.1 fcntl1.3.2 实现函数SetNoBlock1.3.3 轮询方式读取标准输入1.3.4 I/O多路转接之select1.3.4.1 初识select&#xff1a;1.3.4.2 select函数原型1.3.4.3 理…...

notepad++技巧:查找和替换:扩展 or 正则表达式

notepad 有很多优点&#xff1a;多标签&#xff0c;代码高亮&#xff0c;我最喜欢的是查找和替换。 除了可以一次性查找所有打开文件&#xff0c;还可以使用 扩展 or 正则表达式。 例如&#xff1a; 去掉空行&#xff1a;正则表达式&#xff1a; ^\s*$\r\n ^ 表示行首。\s*…...

【图像标注技巧】目标检测图像标注技巧

介绍一些图像标注技巧。之前引用过别人的文章 yolo目标检测 技巧 trick 提升模型性能&#xff0c;deep research检测调研报告也可以进行参考。 拉框类的标注&#xff0c;如果你不确定哪种方法好&#xff0c;你可以把所标注区域的都剪切出来&#xff0c;然后站在屏幕一米之外眯…...

MuJoCo中的机器人状态获取

UR5e机器人xml文件模型 <mujoco model"ur5e"><compiler angle"radian" meshdir"assets" autolimits"true"/><option integrator"implicitfast"/><default><default class"ur5e">&…...

pnpm解决幽灵依赖问题

文章目录 前言1. npm/yarn 现在还有幽灵依赖问题吗&#xff1f;2. pnpm 解决了幽灵依赖问题吗&#xff1f;3. pnpm 是如何解决的&#xff1f;举例说明 1. pnpm 的 node_modules 结构原理结构示意 2. 实际演示幽灵依赖的杜绝步骤1&#xff1a;初始化项目并安装依赖步骤2&#xf…...

测试第四课---------性能测试工具

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…...

frp远程穿透配置

文章目录 准备工作服务端配置(toml)客户端配置(toml)访问内网服务使用ini文件配置 frp是一个高性能的反向代理应用&#xff0c;用于将位于内网的服务通过代理暴露到公网。以下是其基本使用步骤&#xff1a; 准备工作 拥有一台具有公网IP的服务器&#xff0c;作为frp的服务端。…...

【C++】新手入门指南(下)

文章目录 前言 一、引用 1.引用的概念和定义 2.引用的特性 3.引用的使用 4.const引用 5.指针和引用的关系 二、内联函数 三、nullptr 总结 前言 这篇续上篇的内容新手入门指南&#xff08;上&#xff09;&#xff0c;继续带大家学习新知识。如果你感兴趣欢迎订购本专栏。 一、…...

Linux系统编程 day9 SIGCHLD and 线程

SIGCHLD信号 只要子进程信号发生改变&#xff0c;就会产生SIGCHLD信号。 借助SIGCHLD信号回收子进程 回收子进程只跟父进程有关。如果不使用循环回收多个子进程&#xff0c;会产生多个僵尸进程&#xff0c;原因是因为这个信号不会循环等待。 #include<stdio.h> #incl…...

前后端分离项目在未部署条件下如何跨设备通信

其实我此前也不知道这个问题怎么解决&#xff0c;也没有想过—因为做的项目大部分都是前后端分离的&#xff0c;前端直接用后端的部署好的环境就行了。最近也是有点心高气傲开始独立开发&#xff0c;一个人又写前端又写后端也是蛮累的&#xff0c;即使有强有力的cursor也很累很…...

基于Python的多光谱遥感数据处理与分类技术实践—以农作物分类与NDVI评估为例

多光谱遥感数据包含可见光至红外波段的光谱信息&#xff0c;Python凭借其丰富的科学计算库&#xff08;如rasterio、scikit-learn、GDAL&#xff09;&#xff0c;已成为处理此类数据的核心工具。本文以Landsat-8数据为例&#xff0c;演示‌辐射校正→特征提取→监督分类→精度评…...

vscode python 代码无法函数跳转的问题

TL; DR; python.languageServer 配置成了 None 导致 vscode python 代码无法函数跳转 详细信息 mac 环境下 vscode 正常 command 鼠标左键 可以跳转到定义或者使用位置&#xff0c;但是我的为何不知道失效了 我一开始以为是热键冲突&#xff0c;结果发现 mac 好像没办法定…...

SAS宏核心知识与实战应用

1. SAS宏基础 1.1 核心概念 1.1.1 宏处理器 宏处理器在SAS程序运行前执行,用于生成动态代码,可实现代码的灵活定制。 通过宏处理器,可基于输入参数动态生成不同的SAS代码,提高代码复用性。 1.1.2 宏变量 宏变量是存储文本值的容器,用&符号引用,如&var,用于存储…...

Unity 脚本使用(二)——UnityEngine.AI——NavMesh

描述 Singleton class 用于访问被烘培好的 NavMesh. 使用NavMesh类可以执行空间查询&#xff08;spatial queries&#xff09;&#xff0c;例如路径查找和可步行性测试。此类还允许您设置特定区域类型的寻路成本&#xff0c;并调整寻路和避免的全局行为。 静态属性&#xff0…...

从项目真实场景中理解二分算法的细节(附图解和模板)

遇到一个真实场景里使用二分算法的问题&#xff0c;本以为可以放心交给小师弟去做&#xff0c;结果出现了各种问题&#xff0c;在此梳理下二分算法的核心思想和使用细节。 文章目录 1.场景描述2.场景分析3.二分算法的精髓3.1 核心模板3.2 二分过程图解3.3 各种区间写法3.3.1 闭…...

金融图QCPFinancial

QCPFinancial 是 QCustomPlot 中用于绘制金融图表&#xff08;如蜡烛图/K线图&#xff09;的核心类。以下是其关键特性的详细说明&#xff1a; 一、主要属性 属性类型说明dataQSharedPointer<QCPFinancialDataContainer>存储金融数据的数据容器chartStyleQCPFinancial:…...

Jetson Orin NX 16G 配置GO1强化学习运行环境

这一次收到了Jrtson Orin NX, 可以进行部署了。上一次在nano上的失败经验 Jetson nano配置Docker和torch运行环境_jetson docker-CSDN博客 本次的目的是配置cuda-torch-python38环境离机运行策略。 Jetson Orin NX SUPER 1. 烧录镜像 参考链接在ubuntu系统中安装sdk manag…...

文档管理 Document Management

以下是关于项目管理中 文档管理 的深度解析,结合高项(如软考高级信息系统项目管理师)教材内容,系统阐述文档管理的理论框架、核心流程及实战应用: 一、文档管理的基本概念 1. 定义 文档管理是对项目全生命周期中产生的各类文档进行规范化管理的过程,包括创建、存储、版…...