并查集判环(无向图)

该博客介绍了如何使用并查集来检测无向图中的环,并判断图是否连通。在遍历边时,若发现连接的两个节点已经在同一连通块中,则存在环。此外,通过比较顶点数和边数可以确定连通块的环个数。提供的代码示例展示了如何实现这一过程,并解决了一道相关题目:判断每个连通块环的个数是否为1以及图是否连通且无环。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于并查集判环

我们可以使用并查集来判断一个图中是否存在环(此方法仅在无向图中正确):

对于无向图来说,在遍历边 ( u − > v ) (u->v) (u>v) 时,如果结点 u u u 和结点 v v v 的“父亲”相同,那么结点 u u u 和结点 v v v 在同一个环中。

判断无向图是否有环,只需要在合并时加判断即可
每出现一次连接两个已经连通的顶点,图中就会多一个环

而DFS判无向图中连通块环的个数,可以通过点数和边数的个数来判断
设点数 V V V,边数 E E E,如果 V = E + 1 V=E+1 V=E+1,那么就无环, V = E V=E V=E 就是一个环,依次…

例题

1.判断每个连通块环的个数是否为一个
(每个连通块满足顶点和边数相等

2.小希的迷宫
题意:
判断所给图是否连通无环,满足输出 “Yes”,否则输出 “No”
思路:
并查集判断连通并且无环即可
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 998244353;
ll n, m;
int f[maxn];
int x, y;
bool flag = 0;
int find(int x)
{
	int r = x, j, k = x;
	while(r != f[r]) r = f[r]; // 寻找根节点 
	while(k != r)  
	{
		j = f[k];  // 暂存此时k的父节点 
		f[k] = r;  //  将k的父节点改为根节点 
		k = j;  //  k移到父节点   直至全改为根节点  
	}
	return r;
}
void merge(int fx, int fy){
	fx = find(fx); fy = find(fy);
	if(fx != fy) f[fx] = fy;
	else flag = 1;// 连接了两个已经连通的顶点,说明会形成环
}
unordered_map<int,int> ma;
void work()
{
	if(x == 0 && y == 0){// 空图输出Yes 
		cout << "Yes\n";return;
	}
	for(int i = 1; i <= 1e5; ++i) f[i] = i;
	ma[x] = ma[y] = 1;
	merge(x, y);
	while(cin >> x >> y && x != 0){
		ma[x] = ma[y] = 1;
		merge(x, y);
	}
	set<int> se;// 所有出现的顶点的父节点塞进set
	for(int i = 1; i <= 1e5; ++i) if(ma[i])
	{
		int p = find(i);
		se.insert(p);
	}
	if(!flag && se.size() == 1) cout << "Yes\n";
	else cout << "No\n";
	ma.clear();
	flag = 0;
}

int main()
{
	ios::sync_with_stdio(0);
	while(cin >> x >> y && x != -1)
	work();
	return 0;
}
### 并查集算法无向图中检测的原理与实现 并查集(Union-Find)是一种用于处理集合合并与查询问题的数据结构。它可以通过高效的路径压缩和按秩合并操作来无向图中是否存在。以下是其核心原理和实现方法: #### 1. 核心思想 并查集的核心思想是通过维护一组动态变化的不相交集合,将图中的每个连通分量表示为一个集合。如果在遍历边的过程中发现某条边连接的两个顶点已经属于同一个集合,则说明存在。 - **初始化**:每个顶点单独构成一个集合,初始时所有顶点的父节点都指向自身。 - **查找操作**:对于任意顶点,找到它所属集合的根节点,并进行路径压缩以优化后续查找效率。 - **合并操作**:如果两个顶点不属于同一个集合,则将其中一个集合的根节点挂到另一个集合的根节点下,从而将两个集合合并。 #### 2. 算法步骤 以下是一个完整的实现过程: - 初始化一个数组 `pre`,用于记录每个顶点的父节点。初始状态下,每个顶点的父节点是其自身。 - 遍历图的所有边 `(u, v)`: - 如果顶点 `u` 和 `v` 的根节点相同,则说明它们已经在一个集合中,此时添加这条边会导致的出现。 - 如果顶点 `u` 和 `v` 的根节点不同,则将它们所在的两个集合合并。 - 最终,若在整个过程中没有发现任何,则该图是一个无图。 #### 3. 实现代码 以下是一个基于 C++并查集实现示例,用于检测无向图中是否存在: ```cpp #include <iostream> using namespace std; // 查找函数,带路径压缩 int find(int x, int pre[]) { if (x != pre[x]) { pre[x] = find(pre[x], pre); // 路径压缩 } return pre[x]; } // 检测无向图是否有 bool hasCycle(int map[5][5], int n) { int pre[n]; // 记录每个顶点的父节点 for (int i = 0; i < n; i++) { pre[i] = i; // 初始化,每个顶点的父节点是自身 } for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (map[i][j] == 1) { // 若存在边 (i, j) int rootI = find(i, pre); int rootJ = find(j, pre); if (rootI == rootJ) { return true; // 如果根节点相同,说明有 } else { pre[rootI] = rootJ; // 合并两个集合 } } } } return false; // 遍历完所有边后没有发现 } int main() { int map1[5][5] = { {0, 1, 1, 0, 0}, {1, 0, 1, 1, 1}, {1, 1, 0, 0, 0}, {0, 1, 0, 0, 0}, {0, 1, 0, 0, 0} }; int map2[5][5] = { {0, 1, 1, 0, 0}, {1, 0, 0, 1, 1}, {1, 0, 0, 0, 0}, {0, 1, 0, 0, 0}, {0, 1, 0, 0, 0} }; cout << hasCycle(map1, 5) << endl; // 输出 1,有 cout << hasCycle(map2, 5) << endl; // 输出 0,无 return 0; } ``` #### 4. 关键点解释 - **路径压缩**:通过递归的方式,在查找过程中直接将每个节点的父节点设置为其根节点,从而减少后续查找的时间复杂度[^1]。 - **按秩合并**(可选优化):在合并两个集合时,总是将较小的树挂到较大的树上,以保持树的高度尽可能低[^3]。 - **时间复杂度**:单次查找或合并操作的平均时间复杂度接近 O(α(n)),其中 α 是反阿克曼函数,增长极其缓慢[^2]。 #### 5. 总结 使用并查集检测无向图中的是一种高效且优雅的方法。通过维护图中各个连通分量的集合关系,可以在遍历所有边的过程中快速断是否存在
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值