并查集的核心功能,合并集合,查找元素,这两个最基本的功能相关题目本文不列举了,主要是一些和图相关的:
并查集的核心母题
- 一、连通性检测:
- 问题:判断在一个图中,任意两点是否连通。
- 应用:这是并查集最基本的功能,通过 find 操作可以判断两个节点是否属于同一个连通分量。
- 衍生问题:
- 判断两点是否在同一个连通分量中。
- 找出图中有多少个连通分量(即独立的子图)。
- 在图中动态地添加边,并检查连通性。
无向图,任意两点是否连通 问题代码
(注释中输入输出很清晰了)
- 关键!如果在一个连通分量中~根节点会是一样的 ~find值相同
cpp
#include<iostream>
using namespace std;
const int MAXN = 100010;
int fa[MAXN]; // 并查集的父节点数组
// 初始化,每个节点都是自己的父节点
void init(int n) {
for(int i = 1; i <= n; i++)
fa[i] = i;
}
// 查找根节点,并进行路径压缩
int find(int x) {
if (fa[x] != x) {
fa[x] = find(fa[x]);
}
return fa[x];
}
// 合并两个节点所在的集合
void join(int x, int y) {
int fx = find(x);
int fy = find(y);
if(fx != fy)
fa[fx] = fy; // 将其中一个根节点指向另一个
}
// 判断两点是否连通
bool isConnected(int x, int y) {
return find(x) == find(y);
}
int main() {
int n, m;
cin >> n >> m; // n为节点数,m为边数
init(n); // 初始化并查集
// 读取每条边并进行合并
for(int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
join(u, v);
}
// 查询两点是否连通
int a, b;
cin >> a >> b;
if(isConnected(a, b))
cout << "Yes, they are connected." << endl;
else
cout << "No, they are not connected." << endl;
return 0;
}
-
二、连通分量的管理:
- 问题:管理图中的多个连通分量,支持动态的合并与查询操作。
- 应用:这是并查集的核心应用,使用 union 操作可以将两个连通分量合并,通过 find 操作查询某个节点所属的连通分量。
- 衍生问题:
- 求解有多少个连通分量。
- 在合并过程中检测图中是否形成了环。
- 处理动态连通性问题(如动态添加或删除边)。
- 判断图是否为树(只有一个连通分量且无环)。
求解连通分量个数 问题代码:
- 关键:计算有多少个根节点就可以了,所以遍历所有的点,看有多少个点满足fa[i]==i;
cpp
#include<iostream>
using namespace std;
const int MAXN = 100010;
int fa[MAXN]; // 并查集的父节点数组
// 初始化,每个节点都是自己的父节点
void init(int n) {
for(int i = 1; i <= n; i++)
fa[i] = i;
}
// 查找根节点,并进行路径压缩
int find(int x) {
if (fa[x] != x) {
fa[x] = find(fa[x]);
}
return fa[x];
}
// 合并两个节点所在的集合
void join(int x, int y) {
int fx = find(x);
int fy = find(y);
if(fx != fy)
fa[fx] = fy; // 将其中一个根节点指向另一个
}
int main() {
int n, m;
cin >> n >> m; // n为节点数,m为边数
init(n); // 初始化并查集
// 读取每条边并进行合并
for(int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
join(u, v);
}
// 计算连通分量的数量
int components = 0;
for(int i = 1; i <= n; i++) {
if(find(i) == i) // 如果节点是它自己的父节点,说明它是一个连通分量的根
components++;
}
cout << "Number of connected components: " << components << endl;
return 0;
}
- 三、环检测:
- 问题:判断在无向图中是否存在环。
- 应用:在合并过程中,如果发现两个节点已经在同一个连通分量中,那么在添加这条边之后会形成环。
- 衍生问题:
- 在无向图中检测是否有环。
- 在最小生成树算法中(Kruskal),通过避免环的方式构造最小生成树。
- 删除图中冗余的边以防止形成环(如冗余连接问题)。
是否有环 问题代码
- 关键是在join函数中进行判断,正常的逻辑是找到根节点,如果不同就把一个的father指向另一个;
- 而现在是如果相同就说明存在还,如果需要判断的时候就直接返回true就可以啦!
cpp
#include<iostream>
using namespace std;
const int MAXN = 100010;
int fa[MAXN]; // 并查集的父节点数组
// 初始化,每个节点都是自己的父节点
void init(int n) {
for(int i = 1; i <= n; i++)
fa[i] = i;
}
// 查找根节点,并进行路径压缩
int find(int x) {
if (fa[x] != x) {
fa[x] = find(fa[x]);
}
return fa[x];
}
// 合并两个节点所在的集合
bool join(int x, int y) {
int fx = find(x);
int fy = find(y);
if(fx == fy)
return true; // 如果两个节点的根节点相同,说明形成了环
fa[fx] = fy;
return false;
}
int main() {
int n, m;
cin >> n >> m; // n为节点数,m为边数
init(n); // 初始化并查集
bool hasCycle = false;
// 读取每条边并进行合并
for(int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
if(join(u, v)) {
hasCycle = true; // 一旦发现环,标记并停止后续检测
}
}
// 输出是否存在环
if(hasCycle)
cout << "Graph contains a cycle." << endl;
else
cout << "Graph does not contain a cycle." << endl;
return 0;
}
例题:
Problem Description
上次Gardon的迷宫城堡小希玩了很久(见Problem B),现在她也想设计一个迷宫让Gardon来走。但是她设计迷宫的思路不一样,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间A和B,那么既可以通过它从房间A走到房间B,也可以通过它从房间B走到房间A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从5到达8。
Input
输入包含多组数据,每组数据是一个以0 0结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为1,且不超过100000。每两组数据之间有一个空行。
整个文件以两个-1结尾。
Output
对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出"Yes",否则输出"No"。
Sample Input
6 8 5 3 5 2 6 4
5 6 0 0
8 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 0
3 8 6 8 6 4
5 3 5 6 5 2 0 0
-1 -1
Sample Output
Yes
Yes
No
- 分析:判断两个点,1.是不是只有一个连通分量(整体是一个连通图)2.是否有环
- 因为在图中使用点不一定是0开始逐一往上,所以需要visit数组来辅助我们判断
cpp
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int vis[1000010], fa[1000010];
int m, n, flag;
// 初始化每个节点的父节点为其自身
void init() {
for(int i = 1; i < 1000010; i++)
fa[i] = i;
}
// 查找根节点并进行路径压缩
int find(int x) {
return fa[x] == x ? x : (fa[x] = find(fa[x]));
}
// 合并两个节点所属的集合
void join(int x, int y) {
int fx = find(x);
int fy = find(y);
if(fx == fy)
flag = 1; // 出现环
else
fa[fx] = fy;
}
int main() {
while(scanf("%d%d", &n, &m) != EOF) {
if(m == 0 && n == 0) {
printf("Yes\n");
continue; // 空树,自动满足条件
}
if(m == -1 && n == -1) break; // 结束输入
memset(vis, 0, sizeof(vis)); // 重置访问数组
init();
flag = 0;
vis[n] = vis[m] = 1; // 标记访问过的节点
join(n, m);
// 继续读入其他边
//首先执行 scanf,然后判断 a | b 的值,如果 a 或 b 不为 0,则进入循环。
while(scanf("%d%d", &n, &m), n | m) {
vis[n] = vis[m] = 1;
join(m, n);
}
int s = 0;
// 检查连通分量的数量
for(int i = 1; i < 1000010; i++) {
if(fa[i] == i && vis[i])
s++;
if(s > 1) { // 超过一个连通分量,不是树
flag = 1;
break;
}
}
// 输出结果
printf(flag ? "No\n" : "Yes\n");
}
return 0;
}